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

#include "euterpe/file_tasks.h"
#include "euterpe/main_wnd.h"

#include "WDL/wdltypes.h"

#define RSE_OPEN_MEDIA_FILES \
  "All supported media files\0*.flac;*.mp3\0" \
  "FLAC files (*.FLAC)\0*.flac\0" \
  "MPEG audio files (*.MP3)\0*.mp3\0" \
  "All files (*.*)\0*.*\0\0"
#define RSE_OPEN_MEDIA_FILE_DEFEXT "mp3"

#define RSE_OPEN_PLAYLISTS \
  "All supported playlists\0*.epl\0" \
  "Euterpe playlists (*.EPL)\0*.epl\0" \
  "All files (*.*)\0*.*\0\0"
#define RSE_OPEN_PLAYLIST_DEFEXT "epl"

#define RSE_SAVE_PLAYLISTS \
  "Euterpe playlists (*.EPL)\0*.epl\0" \
  "All files (*.*)\0*.*\0\0"
#define RSE_SAVE_PLAYLIST_DEFEXT "epl"

static int sort_by_file_path(const void *a, const void *b)
{
  WDL_FastString *s1, *s2;
  s1 = *(WDL_FastString **)a;
  s2 = *(WDL_FastString **)b;
  return stricmp(s1->Get(), s2->Get());
}

RSE_FileTasks::RSE_FileTasks()
  : m_hwnd(NULL)
  , m_play_folder(false)
  , m_play_files(false)
  , m_add_folder(false)
  , m_add_files(false)
  , m_load_pl(false)
  , m_save_pl(false)
  , m_thread(NULL)
  , m_kill_thread(false)
  , m_want_progress(false)
  , m_scanning(false)
  , m_cancel(false)
  , m_idx(0)
  , m_selfiles(NULL)
{}

RSE_FileTasks::~RSE_FileTasks()
{
  StopThread();

  if (m_selfiles) free(m_selfiles);
}

void RSE_FileTasks::SetParent(HWND parent)
{
  m_hwnd = parent;
  StartThread();
}

void RSE_FileTasks::ScanFiles(const char *path, bool empty_file_cache)
{
  WDL_DirScan dir;
  WDL_FastString fn;

  m_scanning = true;

  m_lp.parse(g_media_ext.Get());

  if (empty_file_cache)
  {
    EmptyFileCache();
  }

  if (!dir.First(path))
  {
    do
    {
      if (dir.GetCurrentIsDirectory())
      {
        dir.GetCurrentFullFN(&fn);

        if (strcmp(fn.get_filepart(), ".") &&
            strcmp(fn.get_filepart(), ".."))
        {
          ScanFiles(fn.Get(), false);
        }
      }
      else
      {
        WDL_FastString *str = new WDL_FastString();

        if (str)
        {
          dir.GetCurrentFullFN(str);

          bool valid_ext = false;

          for (int i = 0; i < m_lp.getnumtokens(); i++)
          {
            if (wdl_filename_cmp(str->get_fileext(), m_lp.gettoken_str(i)) == 0)
            {
              valid_ext = true;
              break;
            }
          }

          if (valid_ext)
          {
            m_file_cache.Add(str);
          }
          else
          {
            delete str;
          }
        }
        else
        {
          delete str;
        }
      }
    }
    while (!dir.Next() && !m_cancel);
  }

  qsort(m_file_cache.GetList(), m_file_cache.GetSize(),
    sizeof(WDL_FastString *), sort_by_file_path);

  m_scanning = false;
}

void RSE_FileTasks::PlayFolder()
{
  m_selected_dir.Resize(2048);

  if (WDL_ChooseDirectory(m_hwnd, "Play folder", NULL,
    m_selected_dir.Get(), m_selected_dir.GetSize(), true))
  {
    m_play_folder = true;
  }
}

void RSE_FileTasks::PlayFiles()
{
  if (m_selfiles) { free(m_selfiles); m_selfiles = NULL; }

  m_selfiles = WDL_ChooseFileForOpen(m_hwnd, "Play files", NULL, NULL,
    RSE_OPEN_MEDIA_FILES, RSE_OPEN_MEDIA_FILE_DEFEXT, true, true);

  if (m_selfiles) m_play_files = true;
}

void RSE_FileTasks::AddFolder()
{
  m_selected_dir.Resize(2048);

  if (WDL_ChooseDirectory(m_hwnd, "Add folder", NULL,
    m_selected_dir.Get(), m_selected_dir.GetSize(), true))
  {
    m_add_folder = true;
  }
}

void RSE_FileTasks::AddFiles()
{
  if (m_selfiles) { free(m_selfiles); m_selfiles = NULL; }

  m_selfiles = WDL_ChooseFileForOpen(m_hwnd, "Add files", NULL, NULL,
    RSE_OPEN_MEDIA_FILES, RSE_OPEN_MEDIA_FILE_DEFEXT, true, true);

  if (m_selfiles) m_add_files = true;
}

void RSE_FileTasks::LoadPlayList()
{
  if (m_selfiles) { free(m_selfiles); m_selfiles = NULL; }

  m_selfiles = WDL_ChooseFileForOpen(m_hwnd, "Load playlist", NULL, NULL,
    RSE_OPEN_PLAYLISTS, RSE_OPEN_PLAYLIST_DEFEXT, true, true);

  if (m_selfiles) m_load_pl = true;
}

void RSE_FileTasks::SavePlayList()
{
  char fn[2048];

  if (WDL_ChooseFileForSave(m_hwnd, "Save playlist", NULL, NULL,
    RSE_SAVE_PLAYLISTS, RSE_SAVE_PLAYLIST_DEFEXT, true, fn, sizeof(fn)))
  {
    g_playlist_wnd->SavePlayList(fn);
  }

  //m_save_pl = true;
}

void RSE_FileTasks::OnPlayFolder()
{
  WantProgress();
  ScanFiles(m_selected_dir.Get());

  if (m_cancel)
  {
    EmptyFileCache();
    return;
  }

  g_playlist_wnd->RemoveAll();
  g_playlist_wnd->ClearQueue();

  SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
    WM_SETREDRAW, FALSE, 0);

  for (m_idx = 0; m_idx < m_file_cache.GetSize(); m_idx++)
  {
    m_curfn = m_file_cache.Get(m_idx);
    g_playlist_wnd->AddItem(m_curfn->Get());
    if (m_cancel) break;
  }

  SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
    WM_SETREDRAW, TRUE, 0);

  EmptyFileCache();

  g_main_loop_mutex.Enter();
  g_main_loop->Play(0);
  g_main_loop_mutex.Leave();
}

void RSE_FileTasks::OnPlayFiles()
{
  const char *only_one = m_selfiles;

  while (*only_one++);

  if (*only_one == '\0' && *(only_one + 1) == '\0')
  {
    g_playlist_wnd->RemoveAll();
    g_playlist_wnd->ClearQueue();
    g_playlist_wnd->AddItem(m_selfiles);
  }
  else
  {
    WantProgress();
    g_playlist_wnd->RemoveAll();
    g_playlist_wnd->ClearQueue();

    WDL_FastString path;

    path.Set(m_selfiles);

    char *s = m_selfiles;
    while (*s++);

    do
    {
      WDL_FastString *fn = new WDL_FastString();

      if (fn)
      {
        fn->Set(path.Get());
        fn->Append(WDL_DIRCHAR_STR);
        fn->Append(s);
        m_file_cache.Add(fn);
      }

      while (*s++);
    } while (*s != '\0' && *(s + 1) != '\0');

    SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
      WM_SETREDRAW, FALSE, 0);

    for (m_idx = 0; m_idx < m_file_cache.GetSize(); m_idx++)
    {
      m_curfn = m_file_cache.Get(m_idx);
      g_playlist_wnd->AddItem(m_curfn->Get());
      if (m_cancel) break;
    }

    SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
      WM_SETREDRAW, TRUE, 0);

    EmptyFileCache();
  }

  g_main_loop_mutex.Enter();
  g_main_loop->Play(0);
  g_main_loop_mutex.Leave();
}

void RSE_FileTasks::OnAddFolder()
{
  WantProgress();
  ScanFiles(m_selected_dir.Get());

  if (m_cancel)
  {
    EmptyFileCache();
    return;
  }

  SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
    WM_SETREDRAW, FALSE, 0);

  for (m_idx = 0; m_idx < m_file_cache.GetSize(); m_idx++)
  {
    m_curfn = m_file_cache.Get(m_idx);
    g_playlist_wnd->AddItem(m_curfn->Get());
    if (m_cancel) break;
  }

  SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
    WM_SETREDRAW, TRUE, 0);

  EmptyFileCache();
}

void RSE_FileTasks::OnAddFiles()
{
  const char *only_one = m_selfiles;

  while (*only_one++);

  if (*only_one == '\0' && *(only_one + 1) == '\0')
  {
    g_playlist_wnd->AddItem(m_selfiles);
  }
  else
  {
    WantProgress();

    WDL_FastString path;

    path.Set(m_selfiles);

    char *s = m_selfiles;
    while (*s++);

    do
    {
      WDL_FastString *fn = new WDL_FastString();

      if (fn)
      {
        fn->Set(path.Get());
        fn->Append(WDL_DIRCHAR_STR);
        fn->Append(s);
        m_file_cache.Add(fn);
      }

      while (*s++);
    } while (*s != '\0' && *(s + 1) != '\0');

    SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
      WM_SETREDRAW, FALSE, 0);

    for (m_idx = 0; m_idx < m_file_cache.GetSize(); m_idx++)
    {
      m_curfn = m_file_cache.Get(m_idx);
      g_playlist_wnd->AddItem(m_curfn->Get());
      if (m_cancel) break;
    }

    SendMessage(GetDlgItem(g_playlist_wnd->Handle(), IDC_LIST1),
      WM_SETREDRAW, TRUE, 0);

    EmptyFileCache();
  }
}

void RSE_FileTasks::OnLoadPlayList()
{
  const char *only_one = m_selfiles;

  while (*only_one++);

  if (*only_one == '\0' && *(only_one + 1) == '\0')
  {
    g_main_loop_mutex.Enter();
    g_main_loop->Stop();
    g_main_loop_mutex.Leave();
    g_playlist_wnd->RemoveAll();

    if (!g_playlist_wnd->LoadPlayList(m_selfiles))
    {
      WDL_FastString err;

      err.SetFormatted(2048, "Euterpe was unable to load:\n%s", m_selfiles);

      MessageBox(g_main_wnd->Handle(), err.Get(), "Euterpe error", MB_OK);
    }
  }
  else
  {
    WantProgress();

    WDL_FastString path;

    path.Set(m_selfiles);

    char *s = m_selfiles;
    while (*s++);

    do
    {
      WDL_FastString *fn = new WDL_FastString();

      if (fn)
      {
        fn->Set(path.Get());
        fn->Append(WDL_DIRCHAR_STR);
        fn->Append(s);
        m_file_cache.Add(fn);
      }

      while (*s++);
    } while (*s != '\0' && *(s + 1) != '\0');

    for (m_idx = 0; m_idx < m_file_cache.GetSize(); m_idx++)
    {
      if (!m_idx)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->Stop();
        g_main_loop_mutex.Leave();
        g_playlist_wnd->RemoveAll();
      }

      m_curfn = m_file_cache.Get(m_idx);

      if (!g_playlist_wnd->LoadPlayList(m_curfn->Get()))
      {
        WDL_FastString err;

        err.SetFormatted(2048,
          "Euterpe was unable to load:\n%s",
          m_curfn->Get());

        MessageBox(g_main_wnd->Handle(), err.Get(), "Euterpe error", MB_OK);

        break;
      }

      if (m_cancel) break;
    }

    EmptyFileCache();
  }
}

void RSE_FileTasks::OnSavePlayList()
{}

bool RSE_FileTasks::WantProgressWindow() const
{
  return m_want_progress;
}

const char *RSE_FileTasks::FileInProgress() const
{
  if (m_scanning) return "Preparing...";
  if (m_curfn) return m_curfn->get_filepart();

  return "";
}

int RSE_FileTasks::TotalFiles() const
{
  return m_file_cache.GetSize();
}

int RSE_FileTasks::CurrentIndex() const
{
  return m_idx;
}

void RSE_FileTasks::AbortOperation()
{
  m_cancel = true;
}

void RSE_FileTasks::EmptyFileCache()
{
  m_idx = 0;
  m_curfn = NULL;
  m_file_cache.Empty(true);
  m_want_progress = m_scanning ? true : false;
}

void RSE_FileTasks::WantProgress()
{
  m_cancel = false;
  m_want_progress = true;
}

int RSE_FileTasks::Run()
{
  if (m_play_folder)
  {
    OnPlayFolder();
    m_play_folder = false;
  }
  else if (m_play_files)
  {
    OnPlayFiles();
    m_play_files = false;
  }
  else if (m_add_folder)
  {
    OnAddFolder();
    m_add_folder = false;
  }
  else if (m_add_files)
  {
    OnAddFiles();
    m_add_files = false;
  }
  else if (m_load_pl)
  {
    OnLoadPlayList();
    m_load_pl = false;
  }
  else if (m_save_pl)
  {
    OnSavePlayList();
    m_save_pl = false;
  }

  return 1;
}

void RSE_FileTasks::StartThread()
{
  WDL_ASSERT(m_thread == NULL);

  if (!m_thread)
  {
    unsigned int thread_id;
    m_thread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, (void *)this, 0, &thread_id);
  }
}

void RSE_FileTasks::StopThread()
{
  m_cancel = true;
  m_kill_thread = true;

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

unsigned int WINAPI RSE_FileTasks::ThreadFunction(void *arg)
{
#if defined(_WIN32)
  CoInitialize(NULL);
#endif

  RSE_FileTasks *self = (RSE_FileTasks *)arg;

  if (WDL_NORMALLY(self))
  {
    self->m_kill_thread = false;

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

#if defined(_WIN32)
  CoUninitialize();
#endif

  return 0;
}
