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

#include "RSI/query.h"
#include "RSI/preferences.h"

#include "WDL/fpcmp.h"

#define RSI_QUERY_NO_ACTION 0
#define RSI_QUERY_SEARCH_DB 1

RSI_Query::RSI_Query()
  : m_thread(NULL)
  , m_killthread(false)
  , m_action(0)
  , m_open(false)
  , m_db(NULL)
  , m_stmt(NULL)
  , m_errmsg(NULL)
  , m_step(0)
  , m_querylimit(0)
{
  m_querylimit = g_preferences->GetMediaLibraryQueryLimit();
}

RSI_Query::~RSI_Query()
{
  if (m_thread) Close();
}

bool RSI_Query::Open()
{
  int rc = 0;
  char *errmsg = NULL;

  m_fn.Set(g_setpath.Get());
  m_fn.Append("rsi.db");

  rc = sqlite3_open(m_fn.Get(), &m_db);

  if (WDL_NOT_NORMALLY(rc))
  {
    wdl_log("cannot open sqlite database\n");
    sqlite3_close(m_db);
    m_db = NULL;
    return false;
  }

  const char *sql_create_table =
    "CREATE TABLE IF NOT EXISTS metadata ("
    "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(m_db, sql_create_table, 0, 0, &errmsg);

  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute SQL command (%s)\n", errmsg);
    sqlite3_free(errmsg);
    sqlite3_close(m_db);
    m_db = NULL;
    return false;
  }

  unsigned int thread_id;
  m_thread = (HANDLE)_beginthreadex(NULL, 0,
    ThreadFunc, (void *)this, 0, &thread_id);

  if (WDL_NOT_NORMALLY(!m_thread))
  {
    wdl_log("cannot start database thread\n");
    sqlite3_close(m_db);
    m_thread = m_db = NULL;
    return false;
  }

  int priority = g_preferences->GetDiskIOPriority();
  SetThreadPriority(m_thread, priority);

  m_open = true;

  return true;
}

bool RSI_Query::IsOpen() const
{
  return m_open;
}

void RSI_Query::Close()
{
  WDL_MutexLock lock(&m_mutex);
  m_killthread = true;

  if (WDL_NORMALLY(m_thread))
  {
    WaitForSingleObject(m_thread, INFINITE);
    CloseHandle(m_thread);
    m_thread = NULL;
  }

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

  if (WDL_NORMALLY(m_db))
  {
    sqlite3_close(m_db);
    m_db = NULL;
  }

  m_open = false;
}

bool RSI_Query::Query()
{
  WDL_MutexLock lock(&m_mutex);

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

  m_querytext.Set("");

  const char *sql =
    "SELECT * FROM metadata LIMIT ?1;";

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

  rc = sqlite3_bind_int(m_stmt, 1, m_querylimit);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
  }

  m_step = sqlite3_step(m_stmt);
  return (m_step == SQLITE_ROW);

  //m_action = RSI_QUERY_SEARCH_DB;
}


bool RSI_Query::Query(const char *text)
{
  WDL_MutexLock lock(&m_mutex);

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

  m_querytext.Set(text);
  m_querytext.Insert("%", 0);
  m_querytext.Append("%");

  const char *sql =
    "SELECT * FROM metadata WHERE title LIKE ?1"
    " OR artist LIKE ?2 OR album LIKE ?3 LIMIT ?4;";

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

  rc = sqlite3_bind_text(m_stmt, 1, m_querytext.Get(), -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
  }

  rc = sqlite3_bind_text(m_stmt, 2, m_querytext.Get(), -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
  }

  rc = sqlite3_bind_text(m_stmt, 3, m_querytext.Get(), -1, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
  }

  rc = sqlite3_bind_int(m_stmt, 4, m_querylimit);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
  }

  m_step = sqlite3_step(m_stmt);
  return (m_step == SQLITE_ROW);

  //m_action = RSI_QUERY_SEARCH_DB;
}

bool RSI_Query::Order(const char *col, bool asc)
{
  WDL_MutexLock lock(&m_mutex);

  WDL_FastString sql;

  if (m_querytext.GetLength())
  {
    if (WDL_likely(asc))
    {
      const char *asc =
        "SELECT * FROM metadata WHERE title LIKE ?1"
        " OR artist LIKE ?2 OR album LIKE ?3"
        " ORDER BY %s COLLATE NOCASE ASC LIMIT ?4;";
      sql.SetFormatted(2048, asc, col);
    }
    else
    {
      const char *desc =
        "SELECT * FROM metadata WHERE title LIKE ?1"
        " OR artist LIKE ?2 OR album LIKE ?3"
        " ORDER BY %s COLLATE NOCASE DESC LIMIT ?4;";
      sql.SetFormatted(2048, desc, col);
    }
  }
  else
  {
    if (WDL_likely(asc))
    {
      const char *asc =
        "SELECT * FROM metadata ORDER BY %s COLLATE NOCASE ASC LIMIT ?1;";
      sql.SetFormatted(2048, asc, col);
    }
    else
    {
      const char *desc =
        "SELECT * FROM metadata ORDER BY %s COLLATE NOCASE DESC LIMIT ?1;";
      sql.SetFormatted(2048, desc, col);
    }
  }

  int rc = sqlite3_prepare_v2(m_db, sql.Get(), -1, &m_stmt, NULL);
  if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
  {
    wdl_log("cannot execute sqlite3_prepare_v2\n");
  }

  if (m_querytext.GetLength())
  {
    rc = sqlite3_bind_text(m_stmt, 1, m_querytext.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
    }

    rc = sqlite3_bind_text(m_stmt, 2, m_querytext.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
    }

    rc = sqlite3_bind_text(m_stmt, 3, m_querytext.Get(), -1, NULL);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
    }

    rc = sqlite3_bind_int(m_stmt, 4, m_querylimit);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
    }
  }
  else
  {
    rc = sqlite3_bind_int(m_stmt, 1, m_querylimit);
    if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
    {
      wdl_log("cannot bind value %s\n", sqlite3_errmsg(m_db));
    }
  }

  m_step = sqlite3_step(m_stmt);
  return (m_step == SQLITE_ROW);

  //m_action = RSI_QUERY_SEARCH_DB;
}

bool RSI_Query::HasRow() const
{
  WDL_MutexLock lock(&m_mutex);
  return (m_step == SQLITE_ROW);
}

const char *RSI_Query::GetColumnText(int col)
{
  WDL_MutexLock lock(&m_mutex);

  if (m_stmt)
  {
    return (const char *)sqlite3_column_text(m_stmt, col);
  }

  return "";
}

double RSI_Query::GetColumnDouble(int col)
{
  WDL_MutexLock lock(&m_mutex);

  if (m_stmt)
  {
    return sqlite3_column_double(m_stmt, col);
  }

  return 0.0;
}

WDL_INT64 RSI_Query::GetColumnInt64(int col)
{
  WDL_MutexLock lock(&m_mutex);

  if (m_stmt)
  {
    return sqlite3_column_int64(m_stmt, col);
  }

  return WDL_INT64_CONST(0);
}

void RSI_Query::NextStep()
{
  WDL_MutexLock lock(&m_mutex);
  m_step = sqlite3_step(m_stmt);

  if (m_step != SQLITE_ROW)
  {
    sqlite3_finalize(m_stmt); m_stmt = NULL;
  }
}

int RSI_Query::Run()
{
  switch (m_action)
  {
  case RSI_QUERY_SEARCH_DB:
    {
      //int rc = sqlite3_prepare_v2(m_db, m_modsql.Get(),
      //  m_modsql.GetLength(), &m_stmt, NULL);

      //if (WDL_NOT_NORMALLY(rc != SQLITE_OK))
      //{
      //  wdl_log("cannot execute sqlite3_prepare_v2\n");
      //}

      m_step = sqlite3_step(m_stmt);

      m_action = RSI_QUERY_NO_ACTION;
    }
  }

  return 1;
}

unsigned WINAPI RSI_Query::ThreadFunc(void *arg)
{
  RSI_Query *self = (RSI_Query *)arg;

  if (WDL_NORMALLY(self))
  {
    int sleepstep = g_preferences->GetDiskIOSleepStep();

    self->m_killthread = false;

    while (!self->m_killthread)
    {
      self->m_mutex.Enter();
      while(!self->Run());
      self->m_mutex.Leave();
      Sleep(sleepstep);
    }
  }

  return 0;
}
