// Copyright (c) 2022 Giorgos Vougioukas
//
// The license can be found in the LICENSE file.

#include "thalia/playlist.h"
#include "thalia/main_wnd.h"
#include "thalia/app_info.h"
#include "thalia/query.h"
#include "thalia/plugin.h"

#include "WDL/fpcmp.h"
#include "WDL/MersenneTwister.h"
#include "WDL/fileread.h"

static int s_subitem = -1;
static bool s_reverse = false;
static int s_ret = 0;
static int subitemcmp(const void *a, const void *b)
{
  THA_PlayList::PlayListEntry *aa = *(THA_PlayList::PlayListEntry **)a;
  THA_PlayList::PlayListEntry *bb = *(THA_PlayList::PlayListEntry **)b;

  switch (s_subitem)
  {
  case 0: s_ret = stricmp(aa->type.Get(), bb->type.Get()); break; // type
  case 1: s_ret =  0; break; // index
  case 2: s_ret = stricmp(aa->title.Get(), bb->title.Get()); break; // title
  case 3: s_ret = stricmp(aa->artist.Get(), bb->artist.Get()); break; // artist
  case 4: s_ret = stricmp(aa->album.Get(), bb->album.Get()); break; // album
  case 5:
    {
      if (WDL_DefinitelyLessThan(aa->length, bb->length)) s_ret = -1;
      else if (WDL_DefinitelyGreaterThan(aa->length, bb->length)) s_ret = 1;
      else s_ret = 0;
    } break; // length
  case 6: s_ret = stricmp(aa->genre.Get(), bb->genre.Get()); break; // genre
  case 7:
    {
      if (WDL_DefinitelyLessThan(aa->srate, bb->srate)) s_ret = -1;
      else if (WDL_DefinitelyGreaterThan(aa->srate, bb->srate)) s_ret = 1;
      else s_ret = 0;
    } break; // samplerate
  case 8: s_ret = stricmp(aa->filename.Get(), bb->filename.Get()); break; // filename
  case 9: s_ret = stricmp(aa->filepath.Get(), bb->filepath.Get()); break; // filepath
  case 10: s_ret = stricmp(aa->fileext.Get(), bb->fileext.Get()); break; // fileext
  case 11:
    {
      if (aa->filesize < bb->filesize) s_ret = -1;
      else if (aa->filesize > bb->filesize) s_ret = 1;
      else s_ret = 0;
    } break; // filesize
  case 12: s_ret = stricmp(aa->comment.Get(), bb->comment.Get()); break; // comment
  }

  if (s_reverse) s_ret *= -1;

  return s_ret;
}

THA_PlayList::THA_PlayList()
  : m_entries(NULL)
  , m_clip(NULL)
  , m_wantplaylist(false)
  , m_wantrefresh(false)
  , m_refreshstart(0)
  , m_refreshend(0)
  , m_userplaylists(NULL)
{
  m_entries = new WDL_PtrList<PlayListEntry>;
  m_clip = new WDL_PtrList<PlayListEntry>;
  m_userplaylists = new UserPlayLists(false);
  m_active.pos = -1;
  LoadPlayList();
}

THA_PlayList::~THA_PlayList()
{
  SavePlayList();
  m_entries->Empty(true);
  delete m_entries;
  m_clip->Empty(true);
  delete m_clip;
  DeleteAllUserPlayLists();
  delete m_userplaylists;
}

void THA_PlayList::AddFilepath(const char *filepath)
{
  WDL_MutexLock lock(&m_mutex);

  g_query->GetPlayListEntry(filepath, m_entries);

  AskForPlayList();
}

bool THA_PlayList::AddFilepathDirectly(const char *filepath)
{
  WDL_MutexLock lock(&m_mutex);

  THA_IFileInput *fi = CreateFileInput(filepath);

  if (WDL_unlikely(!fi))
  {
    return false;
  }

  THA_IFileTag *ft = CreateFileTag(filepath);

  if (WDL_unlikely(!ft))
  {
    delete fi;
    return false;
  }

  if (ft && fi)
  {
    THA_PlayList::PlayListEntry *ple =
    new THA_PlayList::PlayListEntry;
    ple->type.Set(ft->GetType());
    if (strlen(ft->GetTitle()))
    {
      ple->title.Set(ft->GetTitle());
    }
    else
    {
      m_strbuf.Set(ft->GetFileName());
      m_strbuf.remove_fileext();
      ple->title.Set(m_strbuf.Get());
    }
    ple->artist.Set(ft->GetArtist());
    ple->album.Set(ft->GetAlbum());
    ple->length = fi->GetLength();
    ple->genre.Set(ft->GetGenre());
    ple->srate = fi->GetSampleRate();
    ple->filename.Set(ft->GetFileName());
    ple->filepath.Set(ft->GetFilePath());
    ple->fileext.Set(ft->GetFileExtension());
    ple->filesize = ft->GetFileSize();
    ple->comment.Set(ft->GetComment());
    m_entries->Add(ple);
    AskForPlayList();

    delete ft;
    delete fi;

    return true;
  }

  return false;
}

void THA_PlayList::AddUserFilepath(const char *filepath)
{
  WDL_MutexLock lock(&m_mutex);

  g_query->GetUserPlayListEntry(filepath, m_entries);

  AskForPlayList();
}

bool THA_PlayList::AddURL(const char *url)
{
  WDL_MutexLock lock(&m_mutex);

  THA_IFileInput *fi = CreateFileInput(url);

  if (fi)
  {
    THA_PlayList::PlayListEntry *ple =
    new THA_PlayList::PlayListEntry;
    ple->type.Set("MP3");
    ple->title.Set(fi->GetFileName());
    ple->artist.Set("");
    ple->album.Set("");
    ple->length = 0.0;
    ple->genre.Set("");
    ple->srate = fi->GetSampleRate();
    ple->filename.Set("");
    ple->filepath.Set(url);
    ple->fileext.Set("");
    ple->filesize = 0;
    ple->comment.Set("");
    m_entries->Add(ple);
    AskForPlayList();

    delete fi;

    return true;
  }

  return false;
}

void THA_PlayList::DeleteAllFilepaths()
{
  WDL_MutexLock lock(&m_mutex);
  m_entries->Empty(true);
}

void THA_PlayList::MoveSelection(WDL_TypedBuf<int> *selection, int destination)
{
  WDL_MutexLock lock(&m_mutex);

  WDL_PtrList<PlayListEntry> moveentries;

  m_refreshstart = m_refreshend = destination;
  for (int i = 0; i < selection->GetSize(); i++)
  {
    m_refreshstart = wdl_min(m_refreshstart -
      selection->GetSize(), selection->Get()[i]);
    m_refreshend = wdl_max(m_refreshend +
      selection->GetSize(), selection->Get()[i]);
  }

  m_refreshstart = wdl_clamp(m_refreshstart, 0, m_entries->GetSize() - 1);
  m_refreshend = wdl_clamp(m_refreshend + 1, 0, m_entries->GetSize());

  for (int i = 0; i < selection->GetSize(); i++)
  {
    int sel = selection->Get()[i] - i;
    moveentries.Add(m_entries->Get(sel));
    m_entries->Delete(sel);
  }

  int dest = destination;
  if (selection->GetSize())
  {
    if (dest > selection->Get()[0])
    {
      dest -= selection->GetSize() - 1;
    }
  }

  for (int i = 0; i < moveentries.GetSize(); i++)
  {
    PlayListEntry *me = moveentries.Get(i);
    m_entries->Insert(dest + i, me);
  }

  moveentries.Empty();
  AskForRefresh();
}

void THA_PlayList::RemoveSelection(WDL_TypedBuf<int> *selection)
{
  WDL_MutexLock lock(&m_mutex);

  for (int i = 0; i < selection->GetSize(); i++)
  {
    int sel = selection->Get()[i] - i;
    m_entries->Delete(sel, true);
  }
}

void THA_PlayList::RemoveIndex(int index)
{
  WDL_MutexLock lock(&m_mutex);
  m_entries->Delete(index, true);
}

void THA_PlayList::Sort(int subitem, bool reverse, bool selected, WDL_TypedBuf<int> *selection)
{
  WDL_MutexLock lock(&m_mutex);

  if (reverse) s_reverse = true;
  else s_reverse = false;
  s_subitem = subitem;

  if (selected)
  {
    WDL_ASSERT(selection);
    for (int i = 0; i < selection->GetSize(); i++)
    {
      if (!i) m_refreshstart = m_refreshend = selection->Get()[i];
      m_refreshstart = wdl_min(m_refreshstart, selection->Get()[i]);
      m_refreshend = wdl_max(m_refreshend, selection->Get()[i]);
    }

    m_refreshend++;
    WDL_ASSERT(m_refreshstart >= 0);
    WDL_ASSERT(m_refreshstart < m_refreshend);

    WDL_PtrList<PlayListEntry> tmp;

    for (int i = 0; i < selection->GetSize(); i++)
    {
      tmp.Add(m_entries->Get(selection->Get()[i]));
    }

    qsort(tmp.GetList(), tmp.GetSize(),
      sizeof(THA_PlayList::PlayListEntry *), subitemcmp);

    WDL_ASSERT(tmp.GetSize() == selection->GetSize());

    for (int i = 0; i < tmp.GetSize(); i++)
    {
      m_entries->Set(selection->Get()[i], tmp.Get(i));
    }
  }
  else
  {
    WDL_ASSERT(!selection);
    qsort(m_entries->GetList(), m_entries->GetSize(),
      sizeof(THA_PlayList::PlayListEntry *), subitemcmp);

    m_refreshstart = 0;
    m_refreshend = m_entries->GetSize();
  }
  AskForRefresh();
}

void THA_PlayList::ReverseSelection(WDL_TypedBuf<int> *selection)
{
  WDL_MutexLock lock(&m_mutex);

  for (int i = 0; i < selection->GetSize(); i++)
  {
    if (!i) m_refreshstart = m_refreshend = selection->Get()[i];
    m_refreshstart = wdl_min(m_refreshstart, selection->Get()[i]);
    m_refreshend = wdl_max(m_refreshend, selection->Get()[i]);
  }

  m_refreshend++;
  WDL_ASSERT(m_refreshstart >= 0);
  WDL_ASSERT(m_refreshstart < m_refreshend);

  WDL_PtrList<PlayListEntry> tmp;

  for (int i = 0; i < selection->GetSize(); i++)
  {
    tmp.Add(m_entries->Get(selection->Get()[i]));
  }

  for (int lo = 0, hi = tmp.GetSize() - 1; lo < hi; lo++, hi--)
  {
    PlayListEntry *t = tmp.Get(lo);
    tmp.Set(lo, tmp.Get(hi));
    tmp.Set(hi, t);
  }

  WDL_ASSERT(tmp.GetSize() == selection->GetSize());

  for (int i = 0; i < tmp.GetSize(); i++)
  {
    m_entries->Set(selection->Get()[i], tmp.Get(i));
  }

  AskForRefresh();
}

void THA_PlayList::RandomizeSelection(WDL_TypedBuf<int> *selection)
{
  WDL_MutexLock lock(&m_mutex);

  for (int i = 0; i < selection->GetSize(); i++)
  {
    if (!i) m_refreshstart = m_refreshend = selection->Get()[i];
    m_refreshstart = wdl_min(m_refreshstart, selection->Get()[i]);
    m_refreshend = wdl_max(m_refreshend, selection->Get()[i]);
  }

  m_refreshend++;
  WDL_ASSERT(m_refreshstart >= 0);
  WDL_ASSERT(m_refreshstart < m_refreshend);

  WDL_PtrList<PlayListEntry> tmp;

  for (int i = 0; i < selection->GetSize(); i++)
  {
    tmp.Add(m_entries->Get(selection->Get()[i]));
  }

  WDL_ASSERT(tmp.GetSize() == selection->GetSize());

  MTRand mt;

  for (int i = 0; i < selection->GetSize(); i++)
  {
    int range = tmp.GetSize() - 1;
    int pick = mt.randInt(range);
    m_entries->Set(selection->Get()[i], tmp.Get(pick));
    tmp.Delete(pick);
  }

  AskForRefresh();
}

void THA_PlayList::DeleteAll()
{
  WDL_MutexLock lock(&m_mutex);
  m_entries->Empty(true);
  AskForPlayList();
}

void THA_PlayList::Reload()
{
  WDL_MutexLock lock(&m_mutex);
  AskForPlayList();
}

void THA_PlayList::CutSelection(WDL_TypedBuf<int> *selection)
{
  WDL_MutexLock lock(&m_mutex);

  m_clip->Empty(true);

  for (int i = 0; i < selection->GetSize(); i++)
  {
    int sel = selection->Get()[i] - i;
    m_entries->Get(sel)->active = 0;
    m_clip->Add(m_entries->Get(sel));
    m_entries->Delete(sel);
  }
}

void THA_PlayList::CopySelection(WDL_TypedBuf<int> *selection)
{
  WDL_MutexLock lock(&m_mutex);

  m_clip->Empty(true);

  for (int i = 0; i < selection->GetSize(); i++)
  {
    int sel = selection->Get()[i];
    PlayListEntry *ple = m_entries->Get(sel);
    PlayListEntry *nple = new PlayListEntry;
    nple->album.Set(ple->album.Get());
    nple->artist.Set(ple->artist.Get());
    nple->comment.Set(ple->comment.Get());
    nple->fileext.Set(ple->fileext.Get());
    nple->filename.Set(ple->filename.Get());
    nple->filepath.Set(ple->filepath.Get());
    nple->filesize = ple->filesize;
    nple->genre.Set(ple->genre.Get());
    nple->length = ple->length;
    nple->srate = ple->srate;
    nple->title.Set(ple->title.Get());
    nple->type.Set(ple->type.Get());
    nple->active = 0;
    m_clip->Add(nple);
  }
}

void THA_PlayList::Paste(int destination)
{
  WDL_MutexLock lock(&m_mutex);

  for (int i = 0; i < m_clip->GetSize(); i++)
  {
    PlayListEntry *ple = m_clip->Get(i);
    PlayListEntry *nple = new PlayListEntry;
    nple->album.Set(ple->album.Get());
    nple->artist.Set(ple->artist.Get());
    nple->comment.Set(ple->comment.Get());
    nple->fileext.Set(ple->fileext.Get());
    nple->filename.Set(ple->filename.Get());
    nple->filepath.Set(ple->filepath.Get());
    nple->filesize = ple->filesize;
    nple->genre.Set(ple->genre.Get());
    nple->length = ple->length;
    nple->srate = ple->srate;
    nple->title.Set(ple->title.Get());
    nple->type.Set(ple->type.Get());
    nple->active = ple->active;
    m_entries->Insert(destination + i, nple);
  }
}

int THA_PlayList::GetPasteSize() const
{
  return m_clip->GetSize();
}

WDL_INT64 THA_PlayList::GetClipboardBytes() const
{
  WDL_INT64 sz = 0;
  for (int i = 0; i < m_clip->GetSize(); i++)
  {
    PlayListEntry *ple = m_clip->Get(i);
    sz += ple->album.GetLength();
    sz += ple->artist.GetLength();
    sz += ple->comment.GetLength();
    sz += ple->fileext.GetLength();
    sz += ple->filename.GetLength();
    sz += ple->filepath.GetLength();
    sz += sizeof(ple->filesize);
    sz += ple->genre.GetLength();
    sz += sizeof(ple->length);
    sz += sizeof(ple->srate);
    sz += ple->title.GetLength();
    sz += ple->type.GetLength();
    sz += sizeof(ple->active);
  }
  return sz;
}

void THA_PlayList::CopyFilepaths(WDL_PtrList<WDL_FastString> *filepath)
{
  WDL_MutexLock lock(&m_mutex);

  m_clip->Empty(true);

  for (int i = 0; i < filepath->GetSize(); i++)
  {
    WDL_FastString *fp = filepath->Get(i);
    g_query->GetPlayListEntry(fp->Get(), m_clip);
  }
}

void THA_PlayList::CopyUserFilepaths(WDL_PtrList<WDL_FastString> *filepath)
{
  WDL_MutexLock lock(&m_mutex);

  m_clip->Empty(true);

  for (int i = 0; i < filepath->GetSize(); i++)
  {
    WDL_FastString *fp = filepath->Get(i);
    g_query->GetUserPlayListEntry(fp->Get(), m_clip);
  }
}

double THA_PlayList::GetLength(WDL_TypedBuf<int> *selection)
{
  double len = 0.0;
  for (int i = 0; i < selection->GetSize(); i++)
  {
    int sel = selection->Get()[i];
    PlayListEntry *ple = m_entries->Get(sel);
    len += ple->length;
  }
  return len;
}

double THA_PlayList::GetSizeMB(WDL_TypedBuf<int> *selection)
{
  double mb = 0.0;
  for (int i = 0; i < selection->GetSize(); i++)
  {
    int sel = selection->Get()[i];
    PlayListEntry *ple = m_entries->Get(sel);
    mb += (double)ple->filesize / 1024 / 1024;
  }
  return mb;
}

bool THA_PlayList::HasUserPlayList(const char *name)
{
  WDL_MutexLock lock(&m_mutex);
  int rc = 0;
  char *errmsg = NULL;
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;
  bool has_pl = false;

  WDL_FastString fn(g_setpath.Get());
  fn.Append(THA_NAME ".db");

  rc = sqlite3_open(fn.Get(), &db);
  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(db);
    db = NULL;
    return false;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS playlists ("
    "type TEXT NOT NULL,"
    "title TEXT NOT NULL,"
    "artist TEXT NOT NULL,"
    "album TEXT NOT NULL,"
    "length REAL DEFAULT 0.0,"
    "genre TEXT NOT NULL,"
    "samplerate REAL DEFAULT 44100.0,"
    "file_name TEXT NOT NULL,"
    "file_path TEXT NOT NULL,"
    "file_ext TEXT NOT NULL,"
    "file_size INTEGER DEFAULT 0,"
    "comment TEXT NOT NULL,"
    "name TEXT NOT NULL);";

  rc = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return false;
  }

  const char *sql_has_playlist =
    "SELECT * FROM playlists WHERE name = ?1;";

  rc = sqlite3_prepare_v2(db, sql_has_playlist, -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sqlite3_prepare_v2\n");
    sqlite3_close(db);
    db = NULL;
    return false;
  }

  rc = sqlite3_bind_text(stmt, 1, name, -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_step(stmt);
  //if (WDL_NOT_NORMALLY(rc != SQLITE_DONE))
  //{
  //  wdl_log("cannot step statement %s\n", sqlite3_errmsg(db));
  //}

  if (rc == SQLITE_ROW) has_pl = true;

  rc = sqlite3_reset(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot reset statement %s\n", sqlite3_errmsg(db));
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  if (db)
  {
    sqlite3_close(db); db = NULL;
  }

  return has_pl;
}

void THA_PlayList::SaveUserPlayList(const char *name)
{
  WDL_MutexLock lock(&m_mutex);
  int rc = 0;
  char *errmsg = NULL;
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;

  WDL_FastString fn(g_setpath.Get());
  fn.Append(THA_NAME ".db");

  rc = sqlite3_open(fn.Get(), &db);
  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS playlists ("
    "type TEXT NOT NULL,"
    "title TEXT NOT NULL,"
    "artist TEXT NOT NULL,"
    "album TEXT NOT NULL,"
    "length REAL DEFAULT 0.0,"
    "genre TEXT NOT NULL,"
    "samplerate REAL DEFAULT 44100.0,"
    "file_name TEXT NOT NULL,"
    "file_path TEXT NOT NULL,"
    "file_ext TEXT NOT NULL,"
    "file_size INTEGER DEFAULT 0,"
    "comment TEXT NOT NULL,"
    "name TEXT NOT NULL);";

  rc = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_delete_playlist =
    "DELETE FROM playlists WHERE name = ?1;";

  rc = sqlite3_prepare_v2(db, sql_delete_playlist, -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sqlite3_prepare_v2\n");
    sqlite3_close(db);
    db = NULL;
    return;
  }

  rc = sqlite3_bind_text(stmt, 1, name, -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_step(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_DONE))
  {
    wdl_log("cannot step statement %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_reset(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot reset statement %s\n", sqlite3_errmsg(db));
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  const char *sql_insert_metadata =
    "INSERT OR REPLACE INTO playlists ("
    "type,"
    "title,"
    "artist,"
    "album,"
    "length,"
    "genre,"
    "samplerate,"
    "file_name,"
    "file_path,"
    "file_ext,"
    "file_size,"
    "comment,"
    "name)"
    " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13);";

  WDL_FastString sqlstr(sql_insert_metadata);
  rc = sqlite3_prepare_v2(db, sqlstr.Get(), -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot prepare statement %s\n", sqlstr.Get());
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
  }

  for (int i = 0; i < m_entries->GetSize(); i++)
  {
    PlayListEntry *ple = m_entries->Get(i);

    rc = sqlite3_bind_text(stmt, 1, ple->type.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 2, ple->title.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 3, ple->artist.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 4, ple->album.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_double(stmt, 5, ple->length);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 6, ple->genre.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_double(stmt, 7, ple->srate);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 8, ple->filename.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 9, ple->filepath.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 10, ple->fileext.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_int64(stmt, 11, ple->filesize);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 12, ple->comment.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 13, name, -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_step(stmt);
    if (WDL_NOT_NORMALLY(rc != SQLITE_DONE))
    {
      wdl_log("cannot step statement %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_reset(stmt);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot reset statement %s\n", sqlite3_errmsg(db));
    }
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  if (db)
  {
    sqlite3_close(db); db = NULL;
  }
}

THA_PlayList::UserPlayLists *THA_PlayList::GetUserPlayLists()
{
  WDL_MutexLock lock(&m_mutex);

  DeleteAllUserPlayLists();

  int rc = 0;
  char *errmsg = NULL;
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;

  WDL_FastString fn(g_setpath.Get());
  fn.Append(THA_NAME ".db");

  rc = sqlite3_open(fn.Get(), &db);
  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(db);
    db = NULL;
    return false;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS playlists ("
    "type TEXT NOT NULL,"
    "title TEXT NOT NULL,"
    "artist TEXT NOT NULL,"
    "album TEXT NOT NULL,"
    "length REAL DEFAULT 0.0,"
    "genre TEXT NOT NULL,"
    "samplerate REAL DEFAULT 44100.0,"
    "file_name TEXT NOT NULL,"
    "file_path TEXT NOT NULL,"
    "file_ext TEXT NOT NULL,"
    "file_size INTEGER DEFAULT 0,"
    "comment TEXT NOT NULL,"
    "name TEXT NOT NULL);";

  rc = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return false;
  }

  const char *sql_playlists =
    "SELECT * FROM playlists;";

  rc = sqlite3_prepare_v2(db, sql_playlists, -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sqlite3_prepare_v2\n");
    sqlite3_close(db);
    db = NULL;
    return false;
  }

  while (sqlite3_step(stmt) == SQLITE_ROW)
  {
    Title *title = m_userplaylists->Get((const char *)sqlite3_column_text(stmt, 12), NULL);
    if (!title) title = new Title;

    bool found = false;

    for (int i = 0; i < title->GetSize(); i++)
    {
      if  (!strcmp((const char *)sqlite3_column_text(stmt, 1),
        title->Get(i)->title.Get())) found = true;
    }

    if (!found)
    {
      TitlePath *tp = new TitlePath;
      tp->title.Set((const char *)sqlite3_column_text(stmt, 1));
      tp->path.Set((const char *)sqlite3_column_text(stmt, 8));
      title->Add(tp);
    }

    m_userplaylists->Insert((const char *)sqlite3_column_text(stmt, 12), title);
  }

  //if (WDL_NOT_NORMALLY(rc != SQLITE_DONE))
  //{
  //  wdl_log("cannot step statement %s\n", sqlite3_errmsg(db));
  //}

  rc = sqlite3_reset(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot reset statement %s\n", sqlite3_errmsg(db));
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  if (db)
  {
    sqlite3_close(db); db = NULL;
  }

  return m_userplaylists;
}

void THA_PlayList::RenameUserPlayList(const char *name, const char *new_name)
{
  WDL_MutexLock lock(&m_mutex);
  int rc = 0;
  char *errmsg = NULL;
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;

  WDL_FastString fn(g_setpath.Get());
  fn.Append(THA_NAME ".db");

  rc = sqlite3_open(fn.Get(), &db);
  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS playlists ("
    "type TEXT NOT NULL,"
    "title TEXT NOT NULL,"
    "artist TEXT NOT NULL,"
    "album TEXT NOT NULL,"
    "length REAL DEFAULT 0.0,"
    "genre TEXT NOT NULL,"
    "samplerate REAL DEFAULT 44100.0,"
    "file_name TEXT NOT NULL,"
    "file_path TEXT NOT NULL,"
    "file_ext TEXT NOT NULL,"
    "file_size INTEGER DEFAULT 0,"
    "comment TEXT NOT NULL,"
    "name TEXT NOT NULL);";

  rc = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_update_playlist =
    "UPDATE playlists SET name = ?1 WHERE name = ?2;";

  WDL_FastString sqlstr(sql_update_playlist);
  rc = sqlite3_prepare_v2(db, sqlstr.Get(), -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot prepare statement %s\n", sqlstr.Get());
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
  }

  rc = sqlite3_bind_text(stmt, 1, new_name, -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_bind_text(stmt, 2, name, -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_step(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_DONE))
  {
    wdl_log("cannot step statement %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_reset(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot reset statement %s\n", sqlite3_errmsg(db));
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  if (db)
  {
    sqlite3_close(db); db = NULL;
  }
}

void THA_PlayList::DeleteUserPlayList(const char *name)
{
  WDL_MutexLock lock(&m_mutex);
  int rc = 0;
  char *errmsg = NULL;
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;

  WDL_FastString fn(g_setpath.Get());
  fn.Append(THA_NAME ".db");

  rc = sqlite3_open(fn.Get(), &db);
  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS playlists ("
    "type TEXT NOT NULL,"
    "title TEXT NOT NULL,"
    "artist TEXT NOT NULL,"
    "album TEXT NOT NULL,"
    "length REAL DEFAULT 0.0,"
    "genre TEXT NOT NULL,"
    "samplerate REAL DEFAULT 44100.0,"
    "file_name TEXT NOT NULL,"
    "file_path TEXT NOT NULL,"
    "file_ext TEXT NOT NULL,"
    "file_size INTEGER DEFAULT 0,"
    "comment TEXT NOT NULL,"
    "name TEXT NOT NULL);";

  rc = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_delete_playlist =
    "DELETE FROM playlists WHERE name = ?1;";

  rc = sqlite3_prepare_v2(db, sql_delete_playlist, -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sqlite3_prepare_v2\n");
    sqlite3_close(db);
    db = NULL;
    return;
  }

  rc = sqlite3_bind_text(stmt, 1, name, -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_step(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_DONE))
  {
    wdl_log("cannot step statement %s\n", sqlite3_errmsg(db));
  }

  rc = sqlite3_reset(stmt);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot reset statement %s\n", sqlite3_errmsg(db));
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  if (db)
  {
    sqlite3_close(db); db = NULL;
  }
}

void THA_PlayList::DeleteAllUserPlayLists()
{
  WDL_MutexLock lock(&m_mutex);

  for (int i = 0; i < m_userplaylists->GetSize(); i++)
  {
    THA_PlayList::Title *ti = m_userplaylists->Enumerate(i);

    ti->Empty(true);
    delete ti;
  }

  m_userplaylists->DeleteAll();
}

void THA_PlayList::SavePlayList()
{
  WDL_MutexLock lock(&m_mutex);
  int rc = 0;
  char *errmsg = NULL;
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;

  WDL_FastString fn(g_setpath.Get());
  fn.Append(THA_NAME ".db");

  rc = sqlite3_open(fn.Get(), &db);
  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS playlist ("
    "type TEXT NOT NULL,"
    "title TEXT NOT NULL,"
    "artist TEXT NOT NULL,"
    "album TEXT NOT NULL,"
    "length REAL DEFAULT 0.0,"
    "genre TEXT NOT NULL,"
    "samplerate REAL DEFAULT 44100.0,"
    "file_name TEXT NOT NULL,"
    "file_path TEXT NOT NULL,"
    "file_ext TEXT NOT NULL,"
    "file_size INTEGER DEFAULT 0,"
    "comment TEXT NOT NULL);";

  rc = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_delete_records =
    "DELETE FROM playlist;";

  rc = sqlite3_exec(db, sql_delete_records, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_insert_metadata =
    "INSERT OR REPLACE INTO playlist ("
    "type,"
    "title,"
    "artist,"
    "album,"
    "length,"
    "genre,"
    "samplerate,"
    "file_name,"
    "file_path,"
    "file_ext,"
    "file_size,"
    "comment)"
    " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12);";

  WDL_FastString sqlstr(sql_insert_metadata);
  rc = sqlite3_prepare_v2(db, sqlstr.Get(), -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot prepare statement %s\n", sqlstr.Get());
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
  }

  for (int i = 0; i < m_entries->GetSize(); i++)
  {
    PlayListEntry *ple = m_entries->Get(i);

    rc = sqlite3_bind_text(stmt, 1, ple->type.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 2, ple->title.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 3, ple->artist.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 4, ple->album.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_double(stmt, 5, ple->length);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 6, ple->genre.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_double(stmt, 7, ple->srate);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 8, ple->filename.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 9, ple->filepath.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 10, ple->fileext.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_int64(stmt, 11, ple->filesize);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_bind_text(stmt, 12, ple->comment.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_step(stmt);
    if (WDL_NOT_NORMALLY(rc != SQLITE_DONE))
    {
      wdl_log("cannot insert statement %s\n", sqlite3_errmsg(db));
    }

    rc = sqlite3_reset(stmt);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(db));
    }
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  if (db)
  {
    sqlite3_close(db); db = NULL;
  }
}

void THA_PlayList::LoadPlayList()
{
  WDL_MutexLock lock(&m_mutex);
  int rc = 0;
  char *errmsg = NULL;
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;

  WDL_FastString fn(g_setpath.Get());
  fn.Append(THA_NAME ".db");

  rc = sqlite3_open(fn.Get(), &db);
  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS playlist ("
    "type TEXT NOT NULL,"
    "title TEXT NOT NULL,"
    "artist TEXT NOT NULL,"
    "album TEXT NOT NULL,"
    "length REAL DEFAULT 0.0,"
    "genre TEXT NOT NULL,"
    "samplerate REAL DEFAULT 44100.0,"
    "file_name TEXT NOT NULL,"
    "file_path TEXT NOT NULL,"
    "file_ext TEXT NOT NULL,"
    "file_size INTEGER DEFAULT 0,"
    "comment TEXT NOT NULL);";

  rc = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sql command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(db);
    db = NULL;
    return;
  }

  const char *sql = "SELECT * FROM playlist;";

  rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sqlite3_prepare_v2\n");
  }

  int step = sqlite3_step(stmt);

  while (step == SQLITE_ROW)
  {
    THA_PlayList::PlayListEntry *ple =
      new THA_PlayList::PlayListEntry;
    ple->type.Set((const char *)sqlite3_column_text(stmt, 0));
    ple->title.Set((const char *)sqlite3_column_text(stmt, 1));
    ple->artist.Set((const char *)sqlite3_column_text(stmt, 2));
    ple->album.Set((const char *)sqlite3_column_text(stmt, 3));
    ple->length = sqlite3_column_double(stmt, 4);
    ple->genre.Set((const char *)sqlite3_column_text(stmt, 5));
    ple->srate = sqlite3_column_double(stmt, 6);
    ple->filename.Set((const char *)sqlite3_column_text(stmt, 7));
    ple->filepath.Set((const char *)sqlite3_column_text(stmt, 8));
    ple->fileext.Set((const char *)sqlite3_column_text(stmt, 9));
    ple->filesize = sqlite3_column_int64(stmt, 10);
    ple->comment.Set((const char *)sqlite3_column_text(stmt, 11));
    m_entries->Add(ple);

    step = sqlite3_step(stmt);
  }

  if (stmt)
  {
    sqlite3_finalize(stmt); stmt = NULL;
  }

  if (db)
  {
    sqlite3_close(db); db = NULL;
  }

  AskForPlayList();
}

void THA_PlayList::AskForRefresh()
{
  WDL_MutexLock lock(&m_mutex);
  if (m_active.pos >= 0)
  {
    for (int i = 0; i < m_entries->GetSize(); i++)
    {
      PlayListEntry *ple = m_entries->Get(i);
      if (!wdl_filename_cmp(m_active.filepath.Get(),
        ple->filepath.Get()))
      {
        m_active.pos = i; break;
      }
    }
  }

  m_wantrefresh = true;
}

void THA_PlayList::AskForPlayList()
{
  WDL_MutexLock lock(&m_mutex);
  if (m_active.pos >= 0)
  {
    for (int i = 0; i < m_entries->GetSize(); i++)
    {
      PlayListEntry *ple = m_entries->Get(i);
      if (!wdl_filename_cmp(m_active.filepath.Get(),
        ple->filepath.Get()))
      {
        m_active.pos = i; break;
      }
    }
  }

  m_wantplaylist = true;
}

bool THA_PlayList::WantPlayList()
{
  m_mutex.Enter();
  if (m_wantplaylist)
  {
    HWND list = GetDlgItem(g_mainwnd->Handle(), IDC_LIST1);
    SendMessage(list, WM_SETREDRAW, FALSE, 0);
  }
  return m_wantplaylist;
}

void THA_PlayList::DoneWithPlayList()
{
  if (m_wantplaylist)
  {
    HWND list = GetDlgItem(g_mainwnd->Handle(), IDC_LIST1);
    SendMessage(list, WM_SETREDRAW, TRUE, 0);
  }
  m_wantplaylist = false;
  m_mutex.Leave();
}

WDL_PtrList<THA_PlayList::PlayListEntry> *THA_PlayList::GetPlayList() const
{
  return m_entries;
}

bool THA_PlayList::WantRefresh()
{
  m_mutex.Enter();
  if (m_wantrefresh)
  {
    HWND list = GetDlgItem(g_mainwnd->Handle(), IDC_LIST1);
    SendMessage(list, WM_SETREDRAW, FALSE, 0);
  }
  return m_wantrefresh;
}

void THA_PlayList::DoneWithRefresh()
{
  if (m_wantrefresh)
  {
    HWND list = GetDlgItem(g_mainwnd->Handle(), IDC_LIST1);
    SendMessage(list, WM_SETREDRAW, TRUE, 0);
  }
  m_wantrefresh = false;
  m_mutex.Leave();
}

void THA_PlayList::SetActiveEntry(int position)
{
  WDL_MutexLock lock(&m_mutex);
  for (int i = 0; i < m_entries->GetSize(); i++)
  {
    PlayListEntry *ple = m_entries->Get(i);
    ple->active = 0;
    if (i == position)
    {
      ple->active = 1;
      m_active.filepath.Set(ple->filepath.Get());
      m_active.pos = i;
    }
  }
}

void THA_PlayList::GetActiveEntry(ActiveEntry *active_entry)
{
  WDL_MutexLock lock(&m_mutex);
  bool found = false;
  for (int i = 0; i < m_entries->GetSize(); i++)
  {
    PlayListEntry *ple = m_entries->Get(i);
    if (ple->active == 1)
    {
      active_entry->pos = i;
      active_entry->filepath.Set(ple->filepath.Get());
      found = true;
      break;
    }
  }
  if (!found)
  {
    active_entry->filepath.Set("");
    active_entry->pos = -1;
  }
}

const char *THA_PlayList::GetNextActiveFilepath()
{
  WDL_MutexLock lock(&m_mutex);

  for (int i = 0; i < m_entries->GetSize(); i++)
  {
    PlayListEntry *ple = m_entries->Get(i);
    if (ple->active == 1)
    {
      ple->active = 0;
      do
      {
        i++;
        if (i < m_entries->GetSize())
        {
          ple = m_entries->Get(i);
          ple->active = 1;
          m_active.pos = i;
          m_active.filepath.Set(ple->filepath.Get());
          return m_active.filepath.Get();
        }
      } while (i < m_entries->GetSize());
    }
  }

  m_active.filepath.Set("");
  m_active.pos = -1;
  return "";
}

WDL_PtrList<THA_PlayList::PlayListEntry> *THA_PlayList::GetClipboard() const
{
  return m_clip;
}
