#import "Playlist.h"
#import "Tune.h"
#import "Playlist.h"
#import "TuneDatabase.h"

@implementation Playlist

@synthesize name, type, saved;
@synthesize abcSearchText, titleSearchText, keySearchText, meterSearchText;
@synthesize database;
@synthesize predicate;
@synthesize primaryKey;

static sqlite3_stmt *init_statement = nil;
static sqlite3_stmt *insert_statement = nil;
static sqlite3_stmt *delete_statement = nil;
static sqlite3_stmt *delete_tunes_statement = nil;
static sqlite3_stmt *add_statement = nil;
static sqlite3_stmt *remove_statement = nil;
static sqlite3_stmt *select_statement = nil;
static sqlite3_stmt *remove_index_statement = nil;
static sqlite3_stmt *select_index_statement = nil;

+ (void) initPlaylists:(TuneDatabase *)database
{
  if (init_statement == nil) {
    const char *sql = "CREATE TABLE IF NOT EXISTS playlist ('pk' INTEGER PRIMARY KEY, 'name' VARCHAR(32), 'type' INTEGER, 'abc' VARCHAR(10), 'title' VARCHAR(16), 'key' VARCHAR(4), 'meter' VARCHAR(3), 'sortOrder' INTEGER );";
    if (sqlite3_exec(database.database, sql, NULL, NULL, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
    }

    sql = "CREATE TABLE IF NOT EXISTS playlistlink ('playlist' INTEGER, 'tune' INTEGER, 'sortOrder' INTEGER);";
    if (sqlite3_exec(database.database, sql, NULL, NULL, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
    }
  }
}

- (id)init {
  self = [super init];
  if ( self ) {
    primaryKey = 0;
    database = nil;
    self.name = @"";
    self.abcSearchText = @"";
    self.titleSearchText = @"";
    self.keySearchText = @"";
    self.meterSearchText = @"";
    type = kPlaylistTypeSimple;
    saved = YES;
  }
  return self;
}

- (id)initWithName:(NSString *)aName abc:(NSString*)abcText title:(NSString*)titleText key:(NSString*)keyText meter:(NSString*)meterText database:(TuneDatabase*)aDatabase
{
  self = [super init];
  if ( self != nil ) {
    if ( aName == nil || [aName length] == 0 )
      aName = @"New Tunebook";
    self.name = aName;
    self.abcSearchText = abcText;
    self.titleSearchText = titleText;
    self.keySearchText = keyText;
    self.meterSearchText = meterText;
    self.database = aDatabase;
    type = kPlaylistTypeSearch;
    saved = NO;
  }
  return self;
}

- (id)initWithPredicate:(NSPredicate*)aPredicate database:(TuneDatabase *)db
{
  self = [super init];
  if ( self != nil ) {
    self.database = db;
    self.name = @"Search Results";
    self.predicate = aPredicate;
    type = kPlaylistTypePredicate;
    saved = NO;
  }
  return self;
}

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

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

    sqlite3_bind_int(init_statement, 1, primaryKey);
    if (sqlite3_step(init_statement) == SQLITE_ROW) {
      self.name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];
      self.type = sqlite3_column_int(init_statement, 1);
      self.abcSearchText = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 2)];
      self.titleSearchText = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 3)];
      self.keySearchText = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 4)];
      self.meterSearchText = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 5)];
    } else {
      self.name = @"No name";
    }

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

- (void)dealloc {
  [name release];
  [database release];
  [abcSearchText release];
  [titleSearchText release];
  [keySearchText release];
  [meterSearchText release];
  [predicate release];
  [super dealloc];
}

- (BOOL)inDatabase
{
  return database != nil;
}

- (void)insertIntoDatabase:(TuneDatabase *)db
{
  self.database = db;
  saved = YES;

  if (insert_statement == nil) {
    static char *sql = "INSERT INTO playlist (name, type, abc, title, key, meter) VALUES(?, ?, ?, ?, ?, ?)";
    if (sqlite3_prepare_v2(database.database, sql, -1, &insert_statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
    }
  }
  sqlite3_bind_text(insert_statement, 1, [name UTF8String], -1, SQLITE_TRANSIENT);
  sqlite3_bind_int(insert_statement, 2, type);
  sqlite3_bind_text(insert_statement, 3, [abcSearchText UTF8String], -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(insert_statement, 4, [titleSearchText UTF8String], -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(insert_statement, 5, [keySearchText UTF8String], -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(insert_statement, 6, [meterSearchText UTF8String], -1, SQLITE_TRANSIENT);
  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.database));
  } else {
    primaryKey = sqlite3_last_insert_rowid(database.database);
  }
//  hydrated = YES;
}

- (void)deleteFromDatabase {
  if (delete_statement == nil) {
    const char *sql = "DELETE FROM playlist WHERE pk=?";
    if (sqlite3_prepare_v2(database.database, sql, -1, &delete_statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.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.database));
  }

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

}

- (void)addTune:(Tune *)tune
{
  if (add_statement == nil) {
    static char *sql = "INSERT INTO playlistlink (playlist, tune) VALUES(?, ?)";
    if (sqlite3_prepare_v2(database.database, sql, -1, &add_statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
    }
  }
  sqlite3_bind_int(add_statement, 1, primaryKey);
  sqlite3_bind_int(add_statement, 2, tune.primaryKey);
  int success = sqlite3_step(add_statement);

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

- (void)removeTuneAtIndex:(int)index
{
  int success = SQLITE_ERROR;

  if (select_index_statement == nil) {
    const char *sql = "SELECT rowid FROM playlistlink WHERE playlist=? ORDER BY sortOrder LIMIT ?,1";
    if (sqlite3_prepare_v2(database.database, sql, -1, &select_index_statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
    }
  }

  sqlite3_bind_int(select_index_statement, 1, primaryKey);
  sqlite3_bind_int(select_index_statement, 2, index);
  success = sqlite3_step(select_index_statement);
  if (success == SQLITE_ROW) {
    int rowid = sqlite3_column_int(select_index_statement, 0);
    if (remove_index_statement == nil) {
      const char *sql = "DELETE FROM playlistlink WHERE rowid=?";
      if (sqlite3_prepare_v2(database.database, sql, -1, &remove_index_statement, NULL) != SQLITE_OK) {
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
      }
    }
    sqlite3_bind_int(remove_index_statement, 1, rowid);
    success = sqlite3_step(remove_index_statement);
    sqlite3_reset(remove_index_statement);
  }
  sqlite3_reset(select_index_statement);

  if (success != SQLITE_DONE) {
    NSAssert1(0, @"Error: failed to delete from database with message '%s'.", sqlite3_errmsg(database.database));
  }
}

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

- (NSMutableArray *)loadTunes
{
  NSMutableArray *tunes = [[NSMutableArray alloc] init];

  if ( type == kPlaylistTypeSearch ) {
    NSArray *allTunes = database.tunes;
    NSMutableArray *dotTunes = nil;
    if ( abcSearchText != nil && [abcSearchText length] != 0 ) {
      dotTunes = [[NSMutableArray alloc] init];
      [database searchForDots:abcSearchText tunes:allTunes results:dotTunes];
      allTunes = dotTunes;
    }
    [database searchForTunesWithTitle:titleSearchText key:keySearchText meter:meterSearchText tunes:allTunes results:tunes];
    if ( dotTunes )
      [dotTunes release];
  } else if ( type == kPlaylistTypePredicate ) {
    NSArray* results = [[database.tunes filteredArrayUsingPredicate:predicate] retain];
    [tunes addObjectsFromArray:results];
  } else {
    const char *sql = "SELECT tune FROM playlistlink WHERE playlist=? ORDER BY sortOrder";
    sqlite3_stmt *statement;
    if ( sqlite3_prepare_v2(database.database, sql, -1, &statement, NULL) == SQLITE_OK ) {
      sqlite3_bind_int(statement, 1, primaryKey);
      while ( sqlite3_step(statement) == SQLITE_ROW ) {
        int tuneKey = sqlite3_column_int(statement, 0);
        Tune *tune = [Tune tuneWithPrimaryKey:tuneKey database:database.database];
        if ( tune ) {
          [tunes addObject:tune];
          [tune release];
        }
      }
    }
    sqlite3_finalize(statement);
  }
#if TARGET_OS_IPHONE
  if ( database.tuneSortOrder != kSortTunesByOrder )
    [Tune sortTunes:tunes by:database.tuneSortOrder];
#endif
  return tunes;
}

- (void)updateName:(NSString *)newName
{
  if ( [newName length] != 0 && ![name isEqualToString:newName] ) {
    self.name = newName;
    sqlite3_stmt *statement;
    const char *sql = "UPDATE playlist SET name=? WHERE pk=?";
    if (sqlite3_prepare_v2(database.database, sql, -1, &statement, NULL) != SQLITE_OK) {
      NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
    }
    sqlite3_bind_text(statement, 1, [name UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_int(statement, 2, primaryKey);

    int success = sqlite3_step(statement);
    sqlite3_reset(statement);
    if (success != SQLITE_DONE) {
      NSAssert1(0, @"Error: failed to update playlist with message '%s'.", sqlite3_errmsg(database.database));
    }
    sqlite3_finalize(statement);
  }
}

- (void)updateSortOrder:(NSArray*)tunes
{
  [database startTransaction];
  int index = 0;
  sqlite3_stmt *statement;
  const char *sql = "UPDATE playlistlink SET sortOrder=? WHERE playlist=? AND tune=?";
  if (sqlite3_prepare_v2(database.database, sql, -1, &statement, NULL) != SQLITE_OK) {
    NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database.database));
  }
  for ( Tune* tune in tunes ) {
    index++;
    sqlite3_bind_int(statement, 1, index);
    sqlite3_bind_int(statement, 2, primaryKey);
    sqlite3_bind_int(statement, 3, tune.primaryKey);

    int success = sqlite3_step(statement);
    sqlite3_reset(statement);
    if (success != SQLITE_DONE) {
      NSAssert1(0, @"Error: failed to update playlist with message '%s'.", sqlite3_errmsg(database.database));
    }
  }
  sqlite3_finalize(statement);
  [database commitTransaction];
}

+ (void)finalizeStatements {
  if (init_statement) {
    sqlite3_finalize(init_statement);
    init_statement = nil;
  }
  if (insert_statement) {
    sqlite3_finalize(insert_statement);
    insert_statement = nil;
  }
  if (delete_statement) {
    sqlite3_finalize(delete_statement);
    delete_statement = nil;
  }
  if (delete_tunes_statement) {
    sqlite3_finalize(delete_tunes_statement);
    delete_tunes_statement = nil;
  }
  if (add_statement) {
    sqlite3_finalize(add_statement);
    add_statement = nil;
  }
  if (remove_statement) {
    sqlite3_finalize(remove_statement);
    remove_statement = nil;
  }
  if (select_statement) {
    sqlite3_finalize(select_statement);
    select_statement = nil;
  }
  if (remove_index_statement) {
    sqlite3_finalize(remove_index_statement);
    remove_index_statement = nil;
  }
  if (select_index_statement) {
    sqlite3_finalize(select_index_statement);
    select_index_statement = nil;
  }
}

- (BOOL)isEditable
{
  return type == kPlaylistTypeSimple;
}
@end
