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

#include "euterpe/properties_wnd.h"
#include "euterpe/app_info.h"
#include "euterpe/euterpe_plugin.h"
#include "euterpe/plugin.h"

#include <string.h>
#include <stdlib.h>

#include "WDL/fpcmp.h"
#include "WDL/wdlstring.h"
#include "WDL/wdlcstring.h"

#define RSE_LOAD_PROPERTIES 1500
#define RSE_LOAD_PROPERTIES_MS 50

RSE_PropertiesWnd::RSE_PropertiesWnd()
  : m_hwnd(NULL)
  , m_list(NULL)
  , m_fns(NULL)
  , m_thread(NULL)
  , m_kill_thread(false)
  , m_idx(0)
{}

RSE_PropertiesWnd::~RSE_PropertiesWnd()
{
  if (m_fns)
  {
    m_fns->Empty(true, free);
    delete m_fns;
  }
}

HWND RSE_PropertiesWnd::Handle() const
{
  return m_hwnd;
}

void RSE_PropertiesWnd::ShowProperties(WDL_PtrList<char> *files, HWND parent)
{
  if (!m_hwnd)
  {
    WDL_ASSERT(m_fns == NULL);
    m_fns = files;

    CreateDialogParam(g_inst,
      MAKEINTRESOURCE(IDD_PROPERTIES),
      parent, RSE_PropertiesWnd::ST_PropertiesWndProc,
      (LPARAM)this);
    ShowWindow(m_hwnd, SW_SHOW);
  }
  else
  {
    if (files)
    {
      files->Empty(true, free);
      delete files;
    }

    SetFocus(m_hwnd);
  }
}

void RSE_PropertiesWnd::ApplyListViewColors(int bgc, int txtc)
{
  if (m_hwnd)
  {
    ListView_SetBkColor(m_list, RGB((bgc >> 16) & 0xFF,
      (bgc >> 8) & 0xFF, (bgc >> 0) & 0xFF));
    ListView_SetTextBkColor(m_list, RGB((bgc >> 16) & 0xFF,
      (bgc >> 8) & 0xFF, (bgc >> 0) & 0xFF));
    ListView_SetTextColor(m_list, RGB((txtc >> 16) & 0xFF,
      (txtc >> 8) & 0xFF, (txtc >> 0) & 0xFF));
    InvalidateRect(m_list, NULL, FALSE);
    InvalidateRect(m_hwnd, NULL, FALSE);
  }
}

int RSE_PropertiesWnd::Run()
{
  if (m_idx < m_fns->GetSize())
  {
    RSE_IFileTagger *ft = CreateFileTagger(m_fns->Get(m_idx));

    if (!ft)
    {
      m_idx++;
      return 0;
    }

    //ft->ReadEverything();

    m_fts.Insert(0, ft);

    m_idx++;
  }

  return 1;
}

unsigned int WINAPI RSE_PropertiesWnd::ThreadFunction(void *arg)
{
  RSE_PropertiesWnd *self = (RSE_PropertiesWnd *)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(10);
    }
  }

  return 0;
}

WDL_DLGRET RSE_PropertiesWnd::ST_PropertiesWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  RSE_PropertiesWnd *self = (RSE_PropertiesWnd *)GetWindowLongPtr(hwnd, GWLP_USERDATA);

  if (!self && msg == WM_INITDIALOG)
  {
    SetWindowLongPtr(hwnd, GWLP_USERDATA, lparam);
    self = (RSE_PropertiesWnd *)lparam;
    self->m_hwnd = hwnd;
  }

  if (self)
  {
    return self->PropertiesWndProc(msg, wparam, lparam);
  }
  else
  {
    return 0;
  }
}

WDL_DLGRET RSE_PropertiesWnd::PropertiesWndProc(UINT msg, WPARAM wparam, LPARAM lparam)
{
#if defined(__linux__)
  if (msg == WM_KEYDOWN && LOWORD(wparam) == VK_RETURN)
  {
    SendMessage(m_hwnd, WM_COMMAND, MAKEWPARAM(IDOK, 0), 0);
    return 0;
  }
#endif

  switch (msg)
  {
  case WM_INITDIALOG:
    {
      m_list = GetDlgItem(m_hwnd, IDC_LIST1);

      m_resize.init(m_hwnd);
      m_resize.init_itemhwnd(m_list, 0.0f, 0.0f, 1.0f, 1.0f);

#if (_WIN32)
      ListView_SetExtendedListViewStyleEx(m_list,
        LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER,
        LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
#else
      ListView_SetExtendedListViewStyleEx(m_list,
        LVS_EX_FULLROWSELECT,
        LVS_EX_FULLROWSELECT);
#endif

      int x = g_ini_file->read_int("properties_wnd_x", 50, EUTERPE_NAME);
      int y = g_ini_file->read_int("properties_wnd_y", 50, EUTERPE_NAME);
      int w = g_ini_file->read_int("properties_wnd_w", 640, EUTERPE_NAME);
      int h = g_ini_file->read_int("properties_wnd_h", 480, EUTERPE_NAME);
      SetWindowPos(m_hwnd, NULL, x, y, w, h, SWP_NOZORDER|SWP_NOACTIVATE);

      ColumnHeader default_column_header[] =
      {
        { 0, "Key", 150, LVCFMT_LEFT, 1, 0 },
        { 1, "Value", 450, LVCFMT_LEFT, 1, 0 }
      };

      m_column_header.Resize(sizeof(default_column_header) /
        sizeof(default_column_header[0]));

      for (int i = 0; i < m_column_header.GetSize(); i++)
      {
        ColumnHeader *ch = m_column_header.Get();
        ch[i] = default_column_header[i];
      }

      LVCOLUMN lvc = { 0, };
      lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;

      WDL_FastString s;

      ColumnHeader *ch = m_column_header.Get();

      for (int i = 0; i < m_column_header.GetSize(); i++)
      {
        s.SetFormatted(128, "properties_width_%02d", i);
        w = g_ini_file->read_int(s.Get(), ch[i].width, EUTERPE_NAME);
        lvc.cx = w;
        lvc.fmt = ch[i].fmt;
        lvc.pszText = (char *)ch[i].text;
        ListView_InsertColumn(m_list, i, &lvc);
      }

      WDL_TypedBuf<int> order_array;
      order_array.Resize(Header_GetItemCount(ListView_GetHeader(m_list)));
      int *oa = order_array.Get();

      for (int i = 0; i < order_array.GetSize(); i++)
      {
        s.SetFormatted(128, "properties_order_%02d", i);
        oa[i] = g_ini_file->read_int(s.Get(), i, EUTERPE_NAME);
      }

      ListView_SetColumnOrderArray(m_list, order_array.GetSize(), order_array.Get());

      int bgc = (0 << 16) | (0 << 8) | (0 << 0);
      bgc = g_ini_file->read_int("pl_bg_color", bgc, "preferences");
      int txtc = (192 << 16) | (192 << 8) | (192 << 0);
      txtc = g_ini_file->read_int("pl_txt_color", txtc, "preferences");

      ListView_SetBkColor(m_list, RGB((bgc >> 16) & 0xFF,
        (bgc >> 8) & 0xFF, (bgc >> 0) & 0xFF));
      ListView_SetTextBkColor(m_list, RGB((bgc >> 16) & 0xFF,
        (bgc >> 8) & 0xFF, (bgc >> 0) & 0xFF));
      ListView_SetTextColor(m_list, RGB((txtc >> 16) & 0xFF,
        (txtc >> 8) & 0xFF, (txtc >> 0) & 0xFF));

      WDL_ASSERT(m_thread == NULL);

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

      SetTimer(m_hwnd, RSE_LOAD_PROPERTIES, RSE_LOAD_PROPERTIES_MS, NULL);
    }
    break;
  case WM_TIMER:
    {
      if (wparam == RSE_LOAD_PROPERTIES && !m_fts.GetSize())
      {
        SetWindowText(m_hwnd, "Properties");
      }

      if (wparam == RSE_LOAD_PROPERTIES && m_fts.GetSize())
      {
        SetWindowText(m_hwnd, "Properties | Loading...");

        SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

        WDL_UTF8_HookListView(m_list);

        LVITEM lvi = { 0, };
        WDL_String sb;
        int itemcount = 0;
        ColumnHeader *ch = m_column_header.Get();

        m_mutex.Enter();
        RSE_IFileTagger *ft = m_fts.Get(m_fts.GetSize() - 1);
        m_fts.Delete(m_fts.GetSize() - 1);
        m_mutex.Leave();

        itemcount = ListView_GetItemCount(m_list);

        if (itemcount)
        {
          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "";
          lvi.cchTextMax = 0;
          ListView_InsertItem(m_list, &lvi);

          itemcount++;
        }

        for (int j = 0; j < m_column_header.GetSize(); j++)
        {
          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = j;
          switch (ch[j].id)
          {
          case 0:
            lvi.mask = LVIF_TEXT | LVIF_PARAM;
            lvi.lParam = (LPARAM)itemcount;
            lvi.pszText = "Filename";
            lvi.cchTextMax = (int)strlen("Filename");
            ListView_InsertItem(m_list, &lvi);
            break;
          case 1:
            lvi.mask = LVIF_TEXT;
            lvi.pszText = (char *)ft->GetFileName();
            lvi.cchTextMax = (int)strlen(ft->GetFileName());
            ListView_SetItem(m_list, &lvi);
            break;
          }
        }

        //for (int k = 0; k < ft->key.GetSize(); k++)
        {
          itemcount++;

          //if (!k)
          {
            lvi.mask = LVIF_TEXT | LVIF_PARAM;
            lvi.lParam = (LPARAM)itemcount;
            lvi.iItem = itemcount;
            lvi.iSubItem = 0;
            lvi.pszText = "## METADATA ##";
            lvi.cchTextMax = (int)strlen("## METADATA ##");
            ListView_InsertItem(m_list, &lvi);

            lvi.mask = LVIF_TEXT;
            lvi.iItem = itemcount;
            lvi.iSubItem = 1;
            lvi.pszText = "## METADATA ##";
            lvi.cchTextMax = (int)strlen("## METADATA ##");;
            ListView_SetItem(m_list, &lvi);

            itemcount++;
          }

          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "Title";
          lvi.cchTextMax = (int)strlen("Title");
          ListView_InsertItem(m_list, &lvi);

          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = 1;
          lvi.pszText = (char *)ft->GetTitle();
          lvi.cchTextMax = (int)strlen(ft->GetTitle());
          ListView_SetItem(m_list, &lvi);

          itemcount++;

          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "Artist";
          lvi.cchTextMax = (int)strlen("Artist");
          ListView_InsertItem(m_list, &lvi);

          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = 1;
          lvi.pszText = (char *)ft->GetArtist();
          lvi.cchTextMax = (int)strlen(ft->GetArtist());
          ListView_SetItem(m_list, &lvi);

          itemcount++;

          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "Album";
          lvi.cchTextMax = (int)strlen("Album");
          ListView_InsertItem(m_list, &lvi);

          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = 1;
          lvi.pszText = (char *)ft->GetAlbum();
          lvi.cchTextMax = (int)strlen(ft->GetAlbum());
          ListView_SetItem(m_list, &lvi);

          itemcount++;

          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "Genre";
          lvi.cchTextMax = (int)strlen("Genre");
          ListView_InsertItem(m_list, &lvi);

          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = 1;
          lvi.pszText = (char *)ft->GetGenre();
          lvi.cchTextMax = (int)strlen(ft->GetGenre());
          ListView_SetItem(m_list, &lvi);

          itemcount++;

          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "Track #";
          lvi.cchTextMax = (int)strlen("Track #");
          ListView_InsertItem(m_list, &lvi);

          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = 1;
          if (ft->GetTrack() == 0)
          {
            sb.Set("");
          }
          else
          {
            sb.SetFormatted(64, "%d", ft->GetTrack());
          }
          lvi.pszText = sb.Get();
          lvi.cchTextMax = sb.GetLength();
          ListView_SetItem(m_list, &lvi);

          itemcount++;

          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "Year";
          lvi.cchTextMax = (int)strlen("Year");
          ListView_InsertItem(m_list, &lvi);

          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = 1;
          if (ft->GetYear() == 0)
          {
            sb.Set("");
          }
          else
          {
            sb.SetFormatted(64, "%d", ft->GetYear());
          }
          lvi.pszText = sb.Get();
          lvi.cchTextMax = sb.GetLength();
          ListView_SetItem(m_list, &lvi);

          itemcount++;

          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.lParam = (LPARAM)itemcount;
          lvi.iItem = itemcount;
          lvi.iSubItem = 0;
          lvi.pszText = "Comment";
          lvi.cchTextMax = (int)strlen("Comment");
          ListView_InsertItem(m_list, &lvi);

          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = 1;
          lvi.pszText = (char *)ft->GetComment();
          lvi.cchTextMax = (int)strlen(ft->GetComment());
          ListView_SetItem(m_list, &lvi);
        }

        itemcount++;

        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)itemcount;
        lvi.iItem = itemcount;
        lvi.iSubItem = 0;
        lvi.pszText = "## GENERAL ##";
        lvi.cchTextMax = (int)strlen("## GENERAL ##");
        ListView_InsertItem(m_list, &lvi);

        lvi.mask = LVIF_TEXT;
        lvi.iItem = itemcount;
        lvi.iSubItem = 1;
        lvi.pszText = "## GENERAL ##";
        lvi.cchTextMax = (int)strlen("## GENERAL ##");
        ListView_SetItem(m_list, &lvi);

        itemcount++;

        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)itemcount;
        lvi.iItem = itemcount;
        lvi.iSubItem = 0;
        lvi.pszText = "Bitrate";
        lvi.cchTextMax = (int)strlen("Bitrate");
        ListView_InsertItem(m_list, &lvi);

        lvi.mask = LVIF_TEXT;
        lvi.iItem = itemcount;
        lvi.iSubItem = 1;
        sb.SetFormatted(128, "%d kbit/s", ft->GetBitRate());
        lvi.pszText = sb.Get();
        lvi.cchTextMax = sb.GetLength();
        ListView_SetItem(m_list, &lvi);

        itemcount++;

        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)itemcount;
        lvi.iItem = itemcount;
        lvi.iSubItem = 0;
        lvi.pszText = "Samplerate";
        lvi.cchTextMax = (int)strlen("Samplerate");
        ListView_InsertItem(m_list, &lvi);

        lvi.mask = LVIF_TEXT;
        lvi.iItem = itemcount;
        lvi.iSubItem = 1;
        sb.SetFormatted(128, "%.0f Hz", ft->GetSampleRate());
        lvi.pszText = sb.Get();
        lvi.cchTextMax = sb.GetLength();
        ListView_SetItem(m_list, &lvi);

        itemcount++;

        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)itemcount;
        lvi.iItem = itemcount;
        lvi.iSubItem = 0;
        lvi.pszText = "Channels";
        lvi.cchTextMax = (int)strlen("Channels");
        ListView_InsertItem(m_list, &lvi);

        lvi.mask = LVIF_TEXT;
        lvi.iItem = itemcount;
        lvi.iSubItem = 1;
        sb.SetFormatted(128, "%d", ft->GetChannels());
        lvi.pszText = sb.Get();
        lvi.cchTextMax = sb.GetLength();
        ListView_SetItem(m_list, &lvi);

        itemcount++;

        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)itemcount;
        lvi.iItem = itemcount;
        lvi.iSubItem = 0;
        lvi.pszText = "Length";
        lvi.cchTextMax = (int)strlen("Length");
        ListView_InsertItem(m_list, &lvi);

        int minutes = 0;
        int seconds = 0;
        double time = 0.0;

        time = ft->GetLength();

        if (WDL_DefinitelyGreaterThan(time, 0.0))
        {
          minutes = (int)time / 60;
          seconds = (int)time % 60;
        }

        sb.SetFormatted(32, "%01d:%02d", minutes, seconds);

        lvi.mask = LVIF_TEXT;
        lvi.iItem = itemcount;
        lvi.iSubItem = 1;
        lvi.pszText = sb.Get();
        lvi.cchTextMax = sb.GetLength();
        ListView_SetItem(m_list, &lvi);

        itemcount++;

        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)itemcount;
        lvi.iItem = itemcount;
        lvi.iSubItem = 0;
        lvi.pszText = "File size";
        lvi.cchTextMax = (int)strlen("File size");
        ListView_InsertItem(m_list, &lvi);

        double mb = 0.0;

        mb = (double)ft->GetFileSize() / 1024 / 1024;

        sb.SetFormatted(32, "%.2f MB", mb);

        lvi.mask = LVIF_TEXT;
        lvi.iItem = itemcount;
        lvi.iSubItem = 1;
        lvi.pszText = sb.Get();
        lvi.cchTextMax = sb.GetLength();
        ListView_SetItem(m_list, &lvi);

        itemcount++;

        for (int j = 0; j < m_column_header.GetSize(); j++)
        {
          lvi.mask = LVIF_TEXT;
          lvi.iItem = itemcount;
          lvi.iSubItem = j;
          switch (ch[j].id)
          {
          case 0:
            lvi.mask = LVIF_TEXT | LVIF_PARAM;
            lvi.lParam = (LPARAM)itemcount;
            lvi.pszText = "File path";
            lvi.cchTextMax = (int)strlen("File path");
            ListView_InsertItem(m_list, &lvi);
            break;
          case 1:
            lvi.mask = LVIF_TEXT;
            lvi.pszText = (char *)ft->GetFilePath();
            lvi.cchTextMax = (int)strlen(ft->GetFilePath());
            ListView_SetItem(m_list, &lvi);
            break;
          }
        }

        SendMessage(m_list, WM_SETREDRAW, TRUE, 0);

        delete ft;
      }
    }
    break;
  case WM_ERASEBKGND:
    {
      return 1;
    }
    break;
  case WM_SIZE:
    {
      if (wparam != SIZE_MINIMIZED)
      {
        m_resize.onResize();

        RECT r;
        GetWindowRect(m_list, &r);
        ScreenToClient(m_hwnd, (LPPOINT)&r);
        ScreenToClient(m_hwnd, ((LPPOINT)&r)+1);
        int fcw = ListView_GetColumnWidth(m_list, 0);
        ListView_SetColumnWidth(m_list, 1, (r.right - r.left) - fcw - 20);
      }
    }
    break;
  case WM_DESTROY:
    {
      KillTimer(m_hwnd, RSE_LOAD_PROPERTIES);

      m_kill_thread = true;

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

      m_idx = 0;

      m_fts.Empty(true);

      RECT r;
      GetWindowRect(m_hwnd, &r);
      g_ini_file->write_int("properties_wnd_x", r.left, EUTERPE_NAME);
      g_ini_file->write_int("properties_wnd_y", r.top, EUTERPE_NAME);
      g_ini_file->write_int("properties_wnd_w", r.right - r.left, EUTERPE_NAME);
      g_ini_file->write_int("properties_wnd_h", r.bottom - r.top, EUTERPE_NAME);

      WDL_FastString s;
      WDL_TypedBuf<int> order_array;

      order_array.Resize(Header_GetItemCount(ListView_GetHeader(m_list)));
      ListView_GetColumnOrderArray(m_list, order_array.GetSize(), order_array.Get());

      int *oa = order_array.Get();

      for (int i = 0; i < order_array.GetSize(); i++)
      {
        s.SetFormatted(128, "properties_order_%02d", i);
        g_ini_file->write_int(s.Get(), oa[i], EUTERPE_NAME);
      }

      for (int i = 0; i < Header_GetItemCount(ListView_GetHeader(m_list)); i++)
      {
        s.SetFormatted(128, "properties_width_%02d", i);
        g_ini_file->write_int(s.Get(), ListView_GetColumnWidth(m_list, i), EUTERPE_NAME);
      }

      if (m_fns)
      {
        m_fns->Empty(true, free);
        delete m_fns;
        m_fns = NULL;
      }

      m_hwnd = NULL;
    }
    break;
  case WM_COMMAND:
    {
      switch(LOWORD(wparam))
      {
      case IDOK:
        {

        }
        break;
      case IDCANCEL:
        {
          DestroyWindow(m_hwnd);
        }
        break;
      }
    }
    break;
  }

  return 0;
}
