#import "Tune.h"

static sqlite3_stmt *insert_statement = nil;
static sqlite3_stmt *init_statement = nil;
static sqlite3_stmt *delete_statement = nil;
static sqlite3_stmt *hydrate_statement = nil;
static sqlite3_stmt *dehydrate_statement = nil;

@implementation Tune

@synthesize key, numSharps, timeSigNum, timeSigDen, rating;

+ (void)finalizeStatements {
  if (insert_statement) {
    sqlite3_finalize(insert_statement);
    insert_statement = nil;
  }
  if (init_statement) {
    sqlite3_finalize(init_statement);
    init_statement = nil;
  }
  if (delete_statement) {
    sqlite3_finalize(delete_statement);
    delete_statement = nil;
  }
  if (hydrate_statement) {
    sqlite3_finalize(hydrate_statement);
    hydrate_statement = nil;
  }
  if (dehydrate_statement) {
    sqlite3_finalize(dehydrate_statement);
    dehydrate_statement = nil;
  }
}

- (id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db {
  self = [super init];
  if ( self ) {
    primaryKey = pk;
    database = db;

    if (init_statement == nil) {
      const char *sql = "SELECT title, key, sharps, timesig, rating FROM tune WHERE pk=?";
      if (sqlite3_prepare_v2(database, sql, -1, &init_statement, NULL) != SQLITE_OK) {
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
      }
    }

    sqlite3_bind_int(init_statement, 1, primaryKey);
    if (sqlite3_step(init_statement) == SQLITE_ROW) {
      self.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];
      const char *k = (const char *)sqlite3_column_text(init_statement, 1);
      if ( k )
        self.key = [NSString stringWithUTF8String:k];
      self.numSharps = sqlite3_column_int(init_statement, 2);
      int ts = sqlite3_column_int(init_statement, 3);
      self.timeSigNum = ts >> 8;
      self.timeSigDen = ts & 0xff;
      self.rating = sqlite3_column_int(init_statement, 4);
    } else {
      self.title = @"No title";
      self.key = @"";
    }

    sqlite3_reset(init_statement);
    dirty = NO;
  }
  return self;
}

- (void)insertIntoDatabase:(sqlite3 *)db {
  database = db;

  if (insert_statement == nil) {
    static char *sql = "INSERT INTO tune (title, dots, timesig, key, sharps, rating) VALUES(?, ?, ?, ?, ?, ?)";
    if (sqlite3_prepare_v2(database, sql, -1, &insert_statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
    }
  }
  sqlite3_bind_text(insert_statement, 1, [title UTF8String], -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(insert_statement, 2, [dots UTF8String], -1, SQLITE_TRANSIENT);
  sqlite3_bind_int(insert_statement, 3, (timeSigNum << 8) | timeSigDen);
  sqlite3_bind_text(insert_statement, 4, [key UTF8String], -1, SQLITE_TRANSIENT);
  sqlite3_bind_int(insert_statement, 5, numSharps);
  sqlite3_bind_int(insert_statement, 6, rating);
  int success = sqlite3_step(insert_statement);

  sqlite3_reset(insert_statement);
  if (success == SQLITE_ERROR) {
    NSAssert1(0, @"Error: failed to insert into the database with message '%s'.", sqlite3_errmsg(database));
  } else {
    primaryKey = sqlite3_last_insert_rowid(database);
  }
  hydrated = YES;
}

- (void)dealloc {
  [title release];
  [key release];
  [dots release];
  [super dealloc];
}

- (void)deleteFromDatabase {
  if (delete_statement == nil) {
    const char *sql = "DELETE FROM tune WHERE pk=?";
    if (sqlite3_prepare_v2(database, sql, -1, &delete_statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
    }
  }
  sqlite3_bind_int(delete_statement, 1, primaryKey);
  int success = sqlite3_step(delete_statement);
  sqlite3_reset(delete_statement);
  if (success != SQLITE_DONE) {
    NSAssert1(0, @"Error: failed to delete from database with message '%s'.", sqlite3_errmsg(database));
  }
}

- (void)hydrate {
  if (hydrated)
    return;
  if (hydrate_statement == nil) {
    const char *sql = "SELECT dots FROM tune WHERE pk=?";
    if (sqlite3_prepare_v2(database, sql, -1, &hydrate_statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
    }
  }

  sqlite3_bind_int(hydrate_statement, 1, primaryKey);
  int success = sqlite3_step(hydrate_statement);
  if (success == SQLITE_ROW) {
    char *str = (char *)sqlite3_column_text(hydrate_statement, 0);
    self.dots = (str) ? [NSString stringWithUTF8String:str] : @"";
  } else {
    self.dots = @"Unknown";
  }
  sqlite3_reset(hydrate_statement);
  hydrated = YES;
  dirty = NO;
}

- (void)dehydrate {
  if (dirty) {
    if (dehydrate_statement == nil) {
      const char *sql = "UPDATE tune SET title=?, dots=?, key='?' WHERE pk=?";
      if (sqlite3_prepare_v2(database, sql, -1, &dehydrate_statement, NULL) != SQLITE_OK) {
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
      }
    }
    sqlite3_bind_text(dehydrate_statement, 1, [title UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(dehydrate_statement, 2, [dots UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(dehydrate_statement, 3, [key UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_int(dehydrate_statement, 4, primaryKey);

    int success = sqlite3_step(dehydrate_statement);
    sqlite3_reset(dehydrate_statement);
    if (success != SQLITE_DONE) {
      NSAssert1(0, @"Error: failed to dehydrate with message '%s'.", sqlite3_errmsg(database));
    }
    dirty = NO;
  }

  [dots release];
  dots = nil;
  [data release];
  data = nil;
  hydrated = NO;
}

#pragma mark Properties

- (NSInteger)primaryKey {
  return primaryKey;
}

- (NSString *)title {
  return title;
}

- (void)setTitle:(NSString *)aString {
  if ((!title && !aString) || (title && aString && [title isEqualToString:aString]))
    return;
  dirty = YES;
  [title release];
  title = [aString copy];
}

- (NSString *)dots {
  return dots;
}

- (void)setDots:(NSString *)aString {
  if ((!dots && !aString) || (dots && aString && [dots isEqualToString:aString]))
    return;
  dirty = YES;
  [dots release];
  dots = [aString copy];
}

- (NSString *)key {
  return key;
}

- (void)setKey:(NSString *)aString {
  if ((!key && !aString) || (key && aString && [key isEqualToString:aString]))
    return;
  dirty = YES;
  [key release];
  key = [aString copy];
}

- (BOOL)dirty {
  return dirty;
}

- (void)setRating:(char)r {
  dirty = YES;
  rating = r;
}

- (char)rating {
  return rating;
}

- (void)setKeySignature:(char)k sharps:(char)n {
  numSharps = n;
  dirty = YES;
}

- (void)setTimeSignature:(int)num denominator:(int)den {
  timeSigNum = num;
  timeSigDen = den;
  dirty = YES;
}

@end

