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

#include "thalia/playlist_wnd.h"
#include "thalia/app_info.h"
#include "thalia/playlist.h"
#include "thalia/main_wnd.h"
#include "thalia/preferences.h"
#include "third_party/sqlite/sqlite3.h"

#include "WDL/time_precise.h"

#define THA_PLAYLIST_NAME_ID 300100
#define THA_PLAYLIST_ITEMS_ID 300101

THA_SavePlayListWnd::THA_SavePlayListWnd()
  : m_hwnd(NULL)
{}

THA_SavePlayListWnd::~THA_SavePlayListWnd()
{
  DestroyWindow(m_hwnd);
}

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

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

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

WDL_DLGRET THA_SavePlayListWnd::SavePlayListWndLoop(UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
    {
      int x = GetPrivateProfileInt(THA_NAME, "savepl_wnd_x", 0, g_inipath.Get());
      int y = GetPrivateProfileInt(THA_NAME, "savepl_wnd_y", 50, g_inipath.Get());
      SetWindowPos(m_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
    } break;
    case WM_DESTROY:
    {
      RECT r;
      GetWindowRect(m_hwnd, &r);

      int x = r.left;
      int y = r.top;

      WDL_FastString xstr, ystr;
      xstr.SetFormatted(32, "%d", x);
      ystr.SetFormatted(32, "%d", y);

      WritePrivateProfileString(THA_NAME, "savepl_wnd_x", xstr.Get(), g_inipath.Get());
      WritePrivateProfileString(THA_NAME, "savepl_wnd_y", ystr.Get(), g_inipath.Get());
      m_hwnd = NULL;
    } break;
    case WM_COMMAND:
    {
      switch(LOWORD(wparam))
      {
      case IDOK:
        {
          char text[1024];
          HWND edit = GetDlgItem(m_hwnd, IDC_EDIT1);
          int len = GetWindowText(edit, text, sizeof(text));
          if (len > 0)
          {
            if (g_playlist->HasUserPlayList(text))
            {
              int reply = MessageBox(m_hwnd,
                "Playlist already exists. Do you want to overwrite?",
                "Playlist", MB_YESNO);
              if (reply == IDYES)
              {
                double wall0 = time_precise();
                g_playlist->SaveUserPlayList(text);
                double wall1 = time_precise();
                m_strbuf.SetFormatted(512, "%s | Playlist saved in %f sec",
                  g_mainwnd->GetOrigTitle(), wall1 - wall0);
                g_mainwnd->SetTitle(m_strbuf.Get());
                if (g_saveplwnd) { delete g_saveplwnd; g_saveplwnd = NULL; }
              }
            }
            else
            {
              double wall0 = time_precise();
              g_playlist->SaveUserPlayList(text);
              double wall1 = time_precise();
              m_strbuf.SetFormatted(512, "%s | Playlist saved in %f sec",
                g_mainwnd->GetOrigTitle(), wall1 - wall0);
              g_mainwnd->SetTitle(m_strbuf.Get());
              if (g_saveplwnd) { delete g_saveplwnd; g_saveplwnd = NULL; }
            }
          }
        } break;
      case IDCANCEL:
        {
          if (g_saveplwnd) { delete g_saveplwnd; g_saveplwnd = NULL; }
        } break;
      }
    } break;
    case WM_PAINT:
    {

    } break;
    case WM_CLOSE:
    {
      if (g_saveplwnd) { delete g_saveplwnd; g_saveplwnd = NULL; }
    } break;
  }

  return 0;
}



THA_LoadPlayListWnd::THA_LoadPlayListWnd()
  : m_hwnd(NULL)
  , m_tree(NULL)
  , m_playlist_menu(NULL)
  , m_list(NULL)
{}

THA_LoadPlayListWnd::~THA_LoadPlayListWnd()
{
  g_playlist->DeleteAllUserPlayLists();
  DestroyWindow(m_hwnd);
}

void THA_LoadPlayListWnd::DatabaseCopy()
{
  HTREEITEM it = TreeView_GetSelection(m_tree);
  if (!it) return;

  TVITEM tvi;
  tvi.hItem = it;
  tvi.mask = TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM;
  char text[2048];
  char lparam[2048];
  tvi.pszText = (char *)text;
  tvi.lParam = (LPARAM)lparam;
  tvi.cchTextMax = sizeof(text);
  TreeView_GetItem(m_tree, &tvi);

  WDL_PtrList<WDL_FastString> fn;

  if (!strcmp((const char *)tvi.lParam, "root"))
  {
    HTREEITEM playlist = TreeView_GetChild(m_tree, it);
    HTREEITEM playlist_sibling = playlist;
    while (playlist_sibling)
    {
      HTREEITEM title_sibling = TreeView_GetChild(m_tree, playlist_sibling);
      while(title_sibling)
      {
        tvi.hItem = title_sibling;
        TreeView_GetItem(m_tree, &tvi);
        fn.Add(new WDL_FastString((const char *)tvi.lParam));
        title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
      }
      playlist_sibling = TreeView_GetNextSibling(m_tree, playlist_sibling);
    }
  }
  else if (!strcmp((const char *)tvi.lParam, "playlist"))
  {
    HTREEITEM title = TreeView_GetChild(m_tree, it);
    HTREEITEM title_sibling = title;
    while(title_sibling)
    {
      tvi.hItem = title_sibling;
      TreeView_GetItem(m_tree, &tvi);
      fn.Add(new WDL_FastString((const char *)tvi.lParam));
      title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
    }
  }
  else
  {
    tvi.hItem = it;
    TreeView_GetItem(m_tree, &tvi);
    fn.Add(new WDL_FastString((const char *)tvi.lParam));
  }

  g_playlist->CopyUserFilepaths(&fn);

  WDL_INT64 sz = 0;
  for (int i = 0; i < fn.GetSize(); i++)
  {
    sz += fn.Get(i)->GetLength();
  }
  m_strbuf.SetFormatted(512, "%s | %" WDL_PRI_INT64 " bytes copied",
    g_mainwnd->GetOrigTitle(), sz);
  g_mainwnd->SetTitle(m_strbuf.Get());

  fn.Empty(true);
}

void THA_LoadPlayListWnd::DatabaseOpenFileLocation()
{
  HTREEITEM it = TreeView_GetSelection(m_tree);
  if (!it) return;

  TVITEM tvi;
  tvi.hItem = it;
  tvi.mask = TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM;
  char text[2048];
  char lparam[2048];
  tvi.pszText = (char *)text;
  tvi.lParam = (LPARAM)lparam;
  tvi.cchTextMax = sizeof(text);
  TreeView_GetItem(m_tree, &tvi);

  WDL_PtrList_DeleteOnDestroy<char> fn(free);

  if (!strcmp((const char *)tvi.lParam, "root"))
  {
    HTREEITEM playlist = TreeView_GetChild(m_tree, it);
    HTREEITEM playlist_sibling = playlist;
    while (playlist_sibling)
    {
      HTREEITEM title_sibling = TreeView_GetChild(m_tree, playlist_sibling);
      while(title_sibling)
      {
        tvi.hItem = title_sibling;
        TreeView_GetItem(m_tree, &tvi);
        fn.Add(strdup((const char *)tvi.lParam)); break;
        title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
      }
      playlist_sibling = TreeView_GetNextSibling(m_tree, playlist_sibling);
    }
  }
  else if (!strcmp((const char *)tvi.lParam, "playlist"))
  {
    HTREEITEM title = TreeView_GetChild(m_tree, it);
    HTREEITEM title_sibling = title;
    while(title_sibling)
    {
      tvi.hItem = title_sibling;
      TreeView_GetItem(m_tree, &tvi);
      fn.Add(strdup((const char *)tvi.lParam)); break;
      title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
    }
  }
  else
  {
    tvi.hItem = it;
    TreeView_GetItem(m_tree, &tvi);
    fn.Add(strdup((const char *)tvi.lParam));
  }

  if (fn.GetSize())
  {
    WDL_FastString tmp(fn.Get(0)); tmp.remove_filepart();
    ShellExecute(m_hwnd, "", "explorer.exe", tmp.Get(), "", SW_SHOWNORMAL);
  }
}

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

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

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

WDL_DLGRET THA_LoadPlayListWnd::LoadPlayListWndLoop(UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
    {
      m_resize.init(m_hwnd);
      m_resize.init_item(IDC_TREE1, 0.0f, 0.0f, 1.0f, 1.0f);

      int x = GetPrivateProfileInt(THA_NAME, "loadpl_wnd_x", 0, g_inipath.Get());
      int y = GetPrivateProfileInt(THA_NAME, "loadpl_wnd_y", 50, g_inipath.Get());
      int w = GetPrivateProfileInt(THA_NAME, "loadpl_wnd_w", 200, g_inipath.Get());
      int h = GetPrivateProfileInt(THA_NAME, "loadpl_wnd_h", 500, g_inipath.Get());
      SetWindowPos(m_hwnd, NULL, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE);

      m_tree = GetDlgItem(m_hwnd , IDC_TREE1);
      m_list = GetDlgItem(g_mainwnd->Handle(), IDC_LIST1);
      m_playlist_menu = (HMENU)GetSubMenu(LoadMenu(g_inst, MAKEINTRESOURCE(IDR_LIBRARY_CONTEXT)), 0);
      WDL_UTF8_HookTreeView(m_tree);

      HTREEITEM parent = NULL;
      HTREEITEM playlist = NULL;
      HTREEITEM title = NULL;

      THA_PlayList::UserPlayLists *upl = g_playlist->GetUserPlayLists();
      m_strbuf.SetFormatted(2048, "All playlists [%d]", upl->GetSize());

      TreeView_DeleteAllItems(m_tree);
      m_tvinsert.hParent = NULL;
      m_tvinsert.hInsertAfter = TVI_LAST;
      m_tvinsert.item.mask = TVIF_TEXT|TVIF_PARAM;
      m_tvinsert.item.pszText = (char *)m_strbuf.Get();
      m_tvinsert.item.cchTextMax = m_strbuf.GetLength();
      m_tvinsert.item.lParam = (LPARAM)"root";
      TreeView_InsertItem(m_tree, &m_tvinsert);
      parent = TreeView_GetChild(m_tree, TVI_ROOT);

      for (int i = 0; i < upl->GetSize(); i++)
      {
        const char *key;
        THA_PlayList::Title *ti = upl->Enumerate(i, &key);

        if (WDL_likely(strlen(key) > 0))
        {
          m_strbuf.SetFormatted(2048, "%s [%d]", key, ti->GetSize());
        }
        else m_strbuf.SetFormatted(2048, "[%d]", ti->GetSize());

        m_tvinsert.hParent = parent;
        m_tvinsert.hInsertAfter = TVI_LAST;
        m_tvinsert.item.mask = TVIF_TEXT|TVIF_PARAM;
        m_tvinsert.item.pszText = (char *)m_strbuf.Get();
        m_tvinsert.item.cchTextMax = m_strbuf.GetLength();
        m_tvinsert.item.lParam = (LPARAM)"playlist";
        playlist = TreeView_InsertItem(m_tree, &m_tvinsert);

        for (int k = 0; k < ti->GetSize(); k++)
        {
          THA_PlayList::TitlePath *tp = ti->Get(k);

          if (WDL_unlikely(tp->title.GetLength() == 0))
          {
            tp->title.SetFormatted(2048, "%d", k + 1);
          }

          m_tvinsert.hParent = playlist;
          m_tvinsert.hInsertAfter = TVI_LAST;
          m_tvinsert.item.mask = TVIF_TEXT|TVIF_PARAM;
          m_tvinsert.item.pszText = (char *)tp->title.Get();
          m_tvinsert.item.cchTextMax = tp->title.GetLength();
          m_tvinsert.item.lParam = (LPARAM)tp->path.Get();
          title = TreeView_InsertItem(m_tree, &m_tvinsert);
        }
      }
      TreeView_Expand(m_tree, parent, TVE_EXPAND);

      int bk = g_preferences->GetBackgroundColor();
      int fg = g_preferences->GetTextColor();
      TreeView_SetBkColor(m_tree, RGB((bk >> 16) & 0xFF,(bk >> 8) & 0xFF, bk & 0xFF));
      TreeView_SetTextColor(m_tree, RGB((fg >> 16) & 0xFF,(fg >> 8) & 0xFF, fg & 0xFF));
    } break;
    case WM_DESTROY:
    {
      RECT r;
      GetWindowRect(m_hwnd, &r);

      int x = r.left;
      int y = r.top;
      int w = r.right - r.left;
      int h = r.bottom - r.top;

      WDL_FastString xstr, ystr, wstr, hstr;
      xstr.SetFormatted(32, "%d", x);
      ystr.SetFormatted(32, "%d", y);
      wstr.SetFormatted(32, "%d", w);
      hstr.SetFormatted(32, "%d", h);

      WritePrivateProfileString(THA_NAME, "loadpl_wnd_x", xstr.Get(), g_inipath.Get());
      WritePrivateProfileString(THA_NAME, "loadpl_wnd_y", ystr.Get(), g_inipath.Get());
      WritePrivateProfileString(THA_NAME, "loadpl_wnd_w", wstr.Get(), g_inipath.Get());
      WritePrivateProfileString(THA_NAME, "loadpl_wnd_h", hstr.Get(), g_inipath.Get());
      m_hwnd = NULL;
    } break;
    case WM_CLOSE:
    {
      if (g_loadplwnd) { delete g_loadplwnd; g_loadplwnd = NULL; }
    } break;
    case WM_COMMAND:
    {
      switch(LOWORD(wparam))
      {
      //case IDOK:
      //  {
      //    if (g_loadplwnd) { delete g_loadplwnd; g_loadplwnd = NULL; }
      //  } break;
      case IDCANCEL:
        {
          if (g_loadplwnd) { delete g_loadplwnd; g_loadplwnd = NULL; }
        } break;
      case ID_DATABASE_ADDTOPLAYLIST:
        {
          HTREEITEM it = TreeView_GetSelection(m_tree);
          if (!it) return 0;

          TVITEM tvi;
          tvi.hItem = it;
          tvi.mask = TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM;
          char text[2048];
          char lparam[2048];
          tvi.pszText = (char *)text;
          tvi.lParam = (LPARAM)lparam;
          tvi.cchTextMax = sizeof(text);
          TreeView_GetItem(m_tree, &tvi);

          double wall0 = time_precise();
          WDL_PtrList_DeleteOnDestroy<char> fn(free);

          if (!strcmp((const char *)tvi.lParam, "root"))
          {
            HTREEITEM playlist = TreeView_GetChild(m_tree, it);
            HTREEITEM playlist_sibling = playlist;
            while (playlist_sibling)
            {
              HTREEITEM title_sibling = TreeView_GetChild(m_tree, playlist_sibling);
              while(title_sibling)
              {
                tvi.hItem = title_sibling;
                TreeView_GetItem(m_tree, &tvi);
                fn.Add(strdup((const char *)tvi.lParam));
                title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
              }
              playlist_sibling = TreeView_GetNextSibling(m_tree, playlist_sibling);
            }
          }
          else if (!strcmp((const char *)tvi.lParam, "playlist"))
          {
            HTREEITEM title_sibling = TreeView_GetChild(m_tree, it);
            while(title_sibling)
            {
              tvi.hItem = title_sibling;
              TreeView_GetItem(m_tree, &tvi);
              fn.Add(strdup((const char *)tvi.lParam));
              title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
            }
          }
          else
          {
            tvi.hItem = it;
            TreeView_GetItem(m_tree, &tvi);
            fn.Add(strdup((const char *)tvi.lParam));
          }

          if (fn.GetSize())
          {
            int start = ListView_GetItemCount(m_list);
            int end = start + fn.GetSize();
            g_mainwnd->SetSelection(start, end);
            for (int i = 0; i < fn.GetSize(); i++)
            {
              g_playlist->AddUserFilepath(fn.Get(i));
            }
            double wall1 = time_precise();
            if (fn.GetSize() == 1)
            {
              m_strbuf.SetFormatted(512, "%s | %d file added in %f sec",
                g_mainwnd->GetOrigTitle(), fn.GetSize(), wall1 - wall0);
              g_mainwnd->SetTitle(m_strbuf.Get());
            }
            else
            {
              m_strbuf.SetFormatted(512, "%s | %d files added in %f sec",
                g_mainwnd->GetOrigTitle(), fn.GetSize(), wall1 - wall0);
              g_mainwnd->SetTitle(m_strbuf.Get());
            }
          }
        } break;
      case ID_DATABASE_ADDTONEWPLAYLIST:
        {
          HTREEITEM it = TreeView_GetSelection(m_tree);
          if (!it) return 0;

          TVITEM tvi;
          tvi.hItem = it;
          tvi.mask = TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM;
          char text[2048];
          char lparam[2048];
          tvi.pszText = (char *)text;
          tvi.lParam = (LPARAM)lparam;
          tvi.cchTextMax = sizeof(text);
          TreeView_GetItem(m_tree, &tvi);

          double wall0 = time_precise();

          WDL_PtrList_DeleteOnDestroy<char> fn(free);

          if (!strcmp((const char *)tvi.lParam, "root"))
          {
            HTREEITEM playlist = TreeView_GetChild(m_tree, it);
            HTREEITEM playlist_sibling = playlist;
            while (playlist_sibling)
            {
              HTREEITEM title_sibling = TreeView_GetChild(m_tree, playlist_sibling);
              while(title_sibling)
              {
                tvi.hItem = title_sibling;
                TreeView_GetItem(m_tree, &tvi);
                fn.Add(strdup((const char *)tvi.lParam));
                title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
              }
              playlist_sibling = TreeView_GetNextSibling(m_tree, playlist_sibling);
            }
          }
          else if (!strcmp((const char *)tvi.lParam, "playlist"))
          {
            HTREEITEM title_sibling = TreeView_GetChild(m_tree, it);
            while(title_sibling)
            {
              tvi.hItem = title_sibling;
              TreeView_GetItem(m_tree, &tvi);
              fn.Add(strdup((const char *)tvi.lParam));
              title_sibling = TreeView_GetNextSibling(m_tree, title_sibling);
            }
          }
          else
          {
            tvi.hItem = it;
            TreeView_GetItem(m_tree, &tvi);
            fn.Add(strdup((const char *)tvi.lParam));
          }

          if (fn.GetSize())
          {
            ListView_DeleteAllItems(m_list);
            g_playlist->DeleteAllFilepaths();
            int start = ListView_GetItemCount(m_list);
            int end = start + fn.GetSize();
            g_mainwnd->SetSelection(start, end);
            for (int i = 0; i < fn.GetSize(); i++)
            {
              g_playlist->AddUserFilepath(fn.Get(i));
            }
            double wall1 = time_precise();
            if (fn.GetSize() == 1)
            {
              m_strbuf.SetFormatted(512, "%s | %d file added in %f sec",
                g_mainwnd->GetOrigTitle(), fn.GetSize(), wall1 - wall0);
              g_mainwnd->SetTitle(m_strbuf.Get());
            }
            else
            {
              m_strbuf.SetFormatted(512, "%s | %d files added in %f sec",
                g_mainwnd->GetOrigTitle(), fn.GetSize(), wall1 - wall0);
              g_mainwnd->SetTitle(m_strbuf.Get());
            }
          }
        } break;
      case ID_DATABASE_COPY:
        {
          DatabaseCopy();
        } break;
      case ID_DATABASE_OPENFILELOCATION:
        {
          DatabaseOpenFileLocation();
        } break;
      }
    } break;
    case WM_NOTIFY:
    {
      switch (LOWORD(wparam))
      {
      case IDC_TREE1:
        {
          switch (((LPNMHDR)lparam)->code)
          {
          case TVN_SELCHANGED:
            {

            } break;
          case NM_RCLICK:
            {
              HTREEITEM nit = TreeView_GetNextItem(((LPNMHDR)lparam)->hwndFrom, 0, TVGN_DROPHILITE);
              if (nit) TreeView_SelectItem(((LPNMHDR)lparam)->hwndFrom, nit);

              HTREEITEM it = TreeView_GetSelection(m_tree);
              if (it)
              {
                POINT pt;
                GetCursorPos(&pt);
                TrackPopupMenu(m_playlist_menu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hwnd, NULL);
              }
            } break;
          }
        } break;
      }
    } break;
    case WM_CONTEXTMENU:
    {
      POINT pt;
      pt.x = GET_X_LPARAM(lparam);
      pt.y = GET_Y_LPARAM(lparam);
      HANDLE hwnd = (HANDLE)wparam;

      if (hwnd == m_tree && pt.x == -1 && pt.y == -1)
      {
        HTREEITEM it = TreeView_GetSelection(m_tree);

        if (it)
        {
          RECT r;
          TreeView_EnsureVisible(m_tree, it);
          TreeView_GetItemRect(m_tree, it, &r, TRUE);

          POINT pt;
          pt.x = r.left;
          pt.y = r.bottom;

          GetClientRect(m_tree, &r);
          if (pt.x < r.left)
          {
            TreeView_GetItemRect(m_tree, it, &r, TRUE);
            pt.x = r.left;
            pt.y = r.bottom;
          }

          ClientToScreen(m_tree, &pt);
          TrackPopupMenu(m_playlist_menu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hwnd, NULL);
        }
      }
    } break;
    case WM_SIZE:
    {
      if (wparam != SIZE_MINIMIZED)
      {
        m_resize.onResize();
      }
    } break;
  }

  return 0;
}



THA_EditPlayListsWnd::THA_EditPlayListsWnd()
  : m_hwnd(NULL)
  , m_edit(NULL)
  , m_playlist_menu(NULL)
{}

THA_EditPlayListsWnd::~THA_EditPlayListsWnd()
{
  g_playlist->DeleteAllUserPlayLists();
  DestroyWindow(m_hwnd);
}

void THA_EditPlayListsWnd::Rename()
{
  int pos = ListView_GetNextItem(m_list, -1, LVIS_SELECTED | LVIS_FOCUSED);
  if (pos >= 0)
  {
    RECT r;
    char text[1024];
    ListView_GetItemRect(m_list, pos, &r, LVIR_LABEL);
    ListView_GetItemText(m_list, pos, 0, text, sizeof(text));
    ListView_SetItemState(m_list, pos, ~LVIS_SELECTED, LVIS_SELECTED)
    m_edit = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
      "Edit", "", WS_CHILDWINDOW | WS_TABSTOP |
      ES_LEFT | ES_AUTOHSCROLL | ES_WANTRETURN,
      r.left, r.top, r.right - r.left, r.bottom - r.top, m_list, NULL, g_inst, 0);
    SendMessage(m_edit, WM_SETFONT, SendMessage(m_hwnd, WM_GETFONT, 0, 0), 0);
    SetWindowText(m_edit, text);
    SendMessage(m_edit, EM_SETSEL, 0, -1);
    ShowWindow(m_edit, SW_SHOW);
    SetFocus(m_edit);
  }
}

HWND THA_EditPlayListsWnd::GetRename() const
{
  return m_edit;
}

void THA_EditPlayListsWnd::RenameConfirm()
{
  char name[1024];
  char newname[1024];
  int len = GetWindowText(m_edit, newname, sizeof(newname));
  if (len > 0)
  {
    int pos = ListView_GetNextItem(m_list, -1, LVIS_FOCUSED);
    if (pos >= 0)
    {
      ListView_GetItemText(m_list, pos, 0, name, sizeof(name));
      ListView_SetItemText(m_list, pos, 0, newname);
      if (!g_playlist->HasUserPlayList(newname))
      {
        g_playlist->RenameUserPlayList(name, newname);
        SetFocus(m_list);
        DestroyWindow(m_edit);
        m_edit = NULL;
        ListView_SetItemState(m_list, pos, LVIS_SELECTED, LVIS_SELECTED);
        ListView_SetItemState(m_list, pos, LVIS_FOCUSED, LVIS_FOCUSED);
        InvalidateRect(m_hwnd, NULL, FALSE);
      }
      else
      {
        MessageBox(m_hwnd,
          "The name already exists. Please choose a different name.",
          "Playlist", MB_OK);
      }
    }
  }
}

void THA_EditPlayListsWnd::RenameAbort()
{
  SetFocus(m_list);
  DestroyWindow(m_edit);
  InvalidateRect(m_hwnd, NULL, FALSE);
  m_edit = NULL;

  int pos = ListView_GetNextItem(m_list, -1, LVIS_FOCUSED);
  if (pos >= 0)
  {
    ListView_SetItemState(m_list, pos, LVIS_SELECTED, LVIS_SELECTED);
    ListView_SetItemState(m_list, pos, LVIS_FOCUSED, LVIS_FOCUSED);
    InvalidateRect(m_list, NULL, FALSE);
  }
}

void THA_EditPlayListsWnd::Delete()
{
  int pos = ListView_GetNextItem(m_list, -1, LVIS_SELECTED | LVIS_FOCUSED);
  if (pos >= 0)
  {
    char text[1024];
    ListView_GetItemText(m_list, pos, 0, text, sizeof(text));
    ListView_DeleteItem(m_list, pos);
    g_playlist->DeleteUserPlayList(text);
  }
}

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

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

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

WDL_DLGRET THA_EditPlayListsWnd::EditPlayListsWndLoop(UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
    {
      m_resize.init(m_hwnd);
      m_resize.init_item(IDC_LIST1, 0.0f, 0.0f, 1.0f, 1.0f);

      int x = GetPrivateProfileInt(THA_NAME, "editpl_wnd_x", 0, g_inipath.Get());
      int y = GetPrivateProfileInt(THA_NAME, "editpl_wnd_y", 50, g_inipath.Get());
      int w = GetPrivateProfileInt(THA_NAME, "editpl_wnd_w", 220, g_inipath.Get());
      int h = GetPrivateProfileInt(THA_NAME, "editpl_wnd_h", 500, g_inipath.Get());
      SetWindowPos(m_hwnd, NULL, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE);

      m_list = GetDlgItem(m_hwnd, IDC_LIST1);
      WDL_UTF8_HookListView(m_list);

      ListView_SetExtendedListViewStyleEx(m_list,
        LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER,
        LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);

      m_playlist_menu = (HMENU)GetSubMenu(LoadMenu(g_inst, MAKEINTRESOURCE(IDR_LIBRARY_CONTEXT)), 4);

      ColumnHeader default_column_header[] =
      {
        { THA_PLAYLIST_NAME_ID, "Name", 140, LVCFMT_LEFT, 1, 0 },
        { THA_PLAYLIST_ITEMS_ID, "Items", 50, LVCFMT_RIGHT, 1, 1 }
      };

      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;

      w = 0;
      WDL_FastString s;

      ColumnHeader *ch = m_column_header.Get();

      for (int i = 0; i < m_column_header.GetSize(); i++)
      {
        s.SetFormatted(128, "playlist_column_width_%02d", i);
        w = GetPrivateProfileInt(THA_NAME, s.Get(), ch[i].width, g_inipath.Get());
        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, "playlist_column_order_%02d", i);
        oa[i] = GetPrivateProfileInt(THA_NAME, s.Get(), i, g_inipath.Get());
      }

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

      THA_PlayList::UserPlayLists *upl = g_playlist->GetUserPlayLists();
      m_strbuf.SetFormatted(2048, "All playlists [%d]", upl->GetSize());

      ListView_DeleteAllItems(m_list);

      for (int i = 0; i < upl->GetSize(); i++)
      {
        const char *key;
        THA_PlayList::Title *ti = upl->Enumerate(i, &key);

        LVITEM lvi = { 0, };
        int si = 0;
        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)i;
        lvi.iItem = i;
        lvi.iSubItem = si++; // name
        lvi.pszText = (char *)key;
        lvi.cchTextMax = (int)strlen(key);
        ListView_InsertItem(m_list, &lvi);

        lvi.mask = LVIF_TEXT;
        lvi.iSubItem = si++; // items
        m_strbuf.SetFormatted(128, "%d", ti->GetSize());
        lvi.pszText = (char *)m_strbuf.Get();
        lvi.cchTextMax = m_strbuf.GetLength();
        ListView_SetItem(m_list, &lvi);
      }

      int bk = g_preferences->GetBackgroundColor();
      int fg = g_preferences->GetTextColor();
      ListView_SetBkColor(m_list, RGB((bk >> 16) & 0xFF,(bk >> 8) & 0xFF, bk & 0xFF));
      ListView_SetTextBkColor(m_list, RGB((bk >> 16) & 0xFF,(bk >> 8) & 0xFF, bk & 0xFF));
      ListView_SetTextColor(m_list, RGB((fg >> 16) & 0xFF,(fg >> 8) & 0xFF, fg & 0xFF));
    } break;
    case WM_DESTROY:
    {
      RECT r;
      GetWindowRect(m_hwnd, &r);

      int x = r.left;
      int y = r.top;
      int w = r.right - r.left;
      int h = r.bottom - r.top;

      WDL_FastString xstr, ystr, wstr, hstr;
      xstr.SetFormatted(32, "%d", x);
      ystr.SetFormatted(32, "%d", y);
      wstr.SetFormatted(32, "%d", w);
      hstr.SetFormatted(32, "%d", h);

      WritePrivateProfileString(THA_NAME, "editpl_wnd_x", xstr.Get(), g_inipath.Get());
      WritePrivateProfileString(THA_NAME, "editpl_wnd_y", ystr.Get(), g_inipath.Get());
      WritePrivateProfileString(THA_NAME, "editpl_wnd_w", wstr.Get(), g_inipath.Get());
      WritePrivateProfileString(THA_NAME, "editpl_wnd_h", hstr.Get(), g_inipath.Get());

      WDL_FastString s;
      WDL_TypedBuf<int> order_array;
      WDL_FastString str;

      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, "playlist_column_order_%02d", i);
        str.SetFormatted(32, "%d", oa[i]);
        WritePrivateProfileString(THA_NAME, s.Get(), str.Get(), g_inipath.Get());
      }

      for (int i = 0; i < Header_GetItemCount(ListView_GetHeader(m_list)); i++)
      {
        s.SetFormatted(128, "playlist_column_width_%02d", i);
        str.SetFormatted(32, "%d", ListView_GetColumnWidth(m_list, i));
        WritePrivateProfileString(THA_NAME, s.Get(), str.Get(), g_inipath.Get());
      }

      m_hwnd = NULL;
    } break;
    case WM_COMMAND:
    {
      switch(LOWORD(wparam))
      {
      //case IDOK:
      //  {
      //    if (g_editplwnd) { delete g_editplwnd; g_editplwnd = NULL; }
      //  } break;
      case IDCANCEL:
        {
          if (g_editplwnd) { delete g_editplwnd; g_editplwnd = NULL; }
        } break;
      case ID_USER_RENAME:
        {
          Rename();
        } break;
      case ID_USER_DELETE:
        {
          Delete();
        } break;
      }
    } break;
    case WM_CLOSE:
      {
        if (g_editplwnd) { delete g_editplwnd; g_editplwnd = NULL; }
      } break;
    case WM_NOTIFY:
    {
      switch (LOWORD(wparam))
      {
      case IDC_LIST1:
        {
          switch (((LPNMHDR)lparam)->code)
          {
          case NM_RCLICK:
            {
              int pos = ListView_GetNextItem(m_list, -1, LVNI_FOCUSED|LVNI_SELECTED);
              if (pos >= 0)
              {
                POINT pt;
                GetCursorPos(&pt);

                TrackPopupMenu(m_playlist_menu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hwnd, NULL);
              }
            } break;
          }
        } break;
      }
    } break;
    case WM_CONTEXTMENU:
    {
      POINT pt;
      pt.x = GET_X_LPARAM(lparam);
      pt.y = GET_Y_LPARAM(lparam);
      HANDLE hwnd = (HANDLE)wparam;

      if (hwnd == m_list && pt.x == -1 && pt.y == -1)
      {
        int pos = ListView_GetNextItem(m_list, -1, LVNI_FOCUSED|LVNI_SELECTED);
        if (pos >= 0)
        {
          RECT r;
          ListView_EnsureVisible(m_list, pos, FALSE);
          ListView_GetItemRect(m_list, pos, &r, LVIR_BOUNDS);
          pt.x = r.left;
          pt.y = r.bottom;

          GetClientRect(m_list, &r);
          if (pt.x < r.left)
          {
            pt.x = r.left;
          }

          ClientToScreen(m_list, (LPPOINT)&pt);
          TrackPopupMenu(m_playlist_menu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hwnd, NULL);
        }
      }
    } break;
    case WM_SIZE:
    {
      if (wparam != SIZE_MINIMIZED)
      {
        int w = LOWORD(lparam);
        ColumnHeader *ch = m_column_header.Get();
        if (ch)
        {
          int c1w = ListView_GetColumnWidth(m_list, 1);
          int c0w = w - 20 - c1w;
          ListView_SetColumnWidth(m_list, 0, c0w);
        }
        m_resize.onResize();
      }
    } break;
  }

  return 0;
}
