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

#include "euterpe/preview_artwork_wnd.h"
#include "euterpe/plugin.h"
#include "euterpe/app_info.h"

#include "WDL/wdlcstring.h"

#define RSE_LOAD_PREVIEW 1510
#define RSE_LOAD_PREVIEW_MS 50

static LOGFONT lf =
{
#if defined(_WIN32)
  12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
  OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH,
  "Arial"
#else
  9, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
  OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH,
  "Arial"
#endif
};

//static void BWfunc(LICE_pixel *p, void *parm)
//{
//  LICE_pixel pix = *p;
//  unsigned char s = (LICE_GETR(pix) + LICE_GETG(pix) + LICE_GETB(pix)) / 3;
//  *p = LICE_RGBA(s, s, s, LICE_GETA(pix));
//}

RSE_SingleImage::RSE_SingleImage()
  : m_img(NULL)
  , m_font(NULL)
  , m_blur(false)
{
  m_font = new LICE_CachedFont;
  m_font->SetFromHFont(CreateFontIndirect(&lf), LICE_FONT_FLAG_OWNS_HFONT);
#ifdef _WIN32
  m_font->SetBkMode(2);
#else
  m_font->SetBkMode(1);
#endif
  m_font->SetBkColor(LICE_RGBA(8, 8, 8, 192));
  m_font->SetTextColor(LICE_RGBA(192, 192, 192, 192));
}

RSE_SingleImage::~RSE_SingleImage()
{
  if (m_img) delete m_img;
  if (m_font) delete m_font;
}

void RSE_SingleImage::OnPaint(LICE_IBitmap *drawbm, int origin_x, int origin_y, RECT *cliprect, int rscale)
{
  WDL_VWnd::OnPaint(drawbm, origin_x, origin_y, cliprect, rscale);

  RECT r, rr;
  GetPosition(&r);

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

  rr.left = r.left;
  rr.right = r.right;
  rr.top = r.top + 188;
  rr.bottom = r.bottom;

  if (m_img)
  {
    LICE_Blit(drawbm, m_img, x, y, 0, 0, m_img->getWidth(),
      m_img->getHeight(), 1.0f, LICE_BLIT_MODE_COPY);
    //if (m_bw) LICE_ProcessRect(drawbm, x, y, w, h, BWfunc, NULL);
    if (m_blur) LICE_Blur(drawbm, m_img, x, y, 0, 0, m_img->getWidth(), m_img->getHeight());
    m_fn.Ellipsize(40, 42);
    m_font->DrawText(drawbm, m_fn.Get(), m_fn.GetLength(), &rr,
      LICE_DT_NEEDALPHA | LICE_DT_USEFGALPHA | DT_NOPREFIX | DT_SINGLELINE);
  }
}

int RSE_SingleImage::OnMouseDown(int xpos, int ypos)
{
  int a = WDL_VWnd::OnMouseDown(xpos, ypos);

  if (!a)
  {
    POINT p;
    GetCursorPos(&p);
    ScreenToClient(GetRealParent(), &p);

    RECT r;
    this->GetPosition(&r);

    if (PtInRect(&r, p))
    {
      m_blur = true;
      WDL_VWnd::RequestRedraw(NULL);

      return 1;
    }
  }

  return a;
}

void RSE_SingleImage::OnMouseUp(int xpos, int ypos)
{
  m_blur = false;
  WDL_VWnd::RequestRedraw(NULL);

  g_main_loop_mutex.Enter();
  g_main_loop->Play(m_track);
  g_main_loop_mutex.Leave();

  WDL_VWnd::OnMouseUp(xpos, ypos);
}

RSE_PreviewArtworkWnd::RSE_PreviewArtworkWnd()
  : m_hwnd(NULL)
  , m_fns(NULL)
  , m_trk(NULL)
  , m_thread(NULL)
  , m_kill_thread(false)
  , m_idx(0)
{}

RSE_PreviewArtworkWnd::~RSE_PreviewArtworkWnd()
{}

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

int RSE_PreviewArtworkWnd::Run()
{
  WDL_ASSERT(m_fns->GetSize() == m_trk->GetSize());

  if (m_idx < m_fns->GetSize())
  {
    WDL_FastString img(m_fns->Get(m_idx));
    LICE_IBitmap *bmp = NULL;

    while (1)
    {
      img.remove_filepart(true);
      img.Append("front.jpg");
      bmp = LICE_LoadJPG(img.Get());
      if (bmp)
      {
        RSE_SingleImage *si = new RSE_SingleImage;
        si->m_img = new LICE_SysBitmap(200, 200);
        LICE_ScaledBlit(si->m_img, bmp, 0, 0, 200,
          200, 0.0, 0.0, (float)bmp->getWidth(),
          (float)bmp->getHeight(), 1.0f, LICE_BLIT_MODE_COPY);
        si->m_fn.Set(WDL_get_filepart(m_fns->Get(m_idx)));
        si->m_track = m_trk->Get()[m_idx];
        m_images.AddChild(si);
        delete bmp;
        break;
      }

      img.remove_filepart(true);
      img.Append("cover.jpg");
      bmp = LICE_LoadJPG(img.Get());
      if (bmp)
      {
        RSE_SingleImage *si = new RSE_SingleImage;
        si->m_img = new LICE_SysBitmap(200, 200);
        LICE_ScaledBlit(si->m_img, bmp, 0, 0, 200,
          200, 0.0, 0.0, (float)bmp->getWidth(),
          (float)bmp->getHeight(), 1.0f, LICE_BLIT_MODE_COPY);
        si->m_fn.Set(WDL_get_filepart(m_fns->Get(m_idx)));
        si->m_track = m_trk->Get()[m_idx];
        m_images.AddChild(si);
        delete bmp;
        break;
      }

      img.Set(m_fns->Get(m_idx));
      img.remove_fileext();
      img.Append(".jpg");
      bmp = LICE_LoadJPG(img.Get());
      if (bmp)
      {
        RSE_SingleImage *si = new RSE_SingleImage;
        si->m_img = new LICE_SysBitmap(200, 200);
        LICE_ScaledBlit(si->m_img, bmp, 0, 0, 200,
          200, 0.0, 0.0, (float)bmp->getWidth(),
          (float)bmp->getHeight(), 1.0f, LICE_BLIT_MODE_COPY);
        si->m_fn.Set(WDL_get_filepart(m_fns->Get(m_idx)));
        si->m_track = m_trk->Get()[m_idx];
        m_images.AddChild(si);
        delete bmp;
        break;
      }

      img.remove_filepart(true);
      img.Append("folder.jpg");
      bmp = LICE_LoadJPG(img.Get());
      if (bmp)
      {
        RSE_SingleImage *si = new RSE_SingleImage;
        si->m_img = new LICE_SysBitmap(200, 200);
        LICE_ScaledBlit(si->m_img, bmp, 0, 0, 200,
          200, 0.0, 0.0, (float)bmp->getWidth(),
          (float)bmp->getHeight(), 1.0f, LICE_BLIT_MODE_COPY);
        si->m_fn.Set(WDL_get_filepart(m_fns->Get(m_idx)));
        si->m_track = m_trk->Get()[m_idx];
        m_images.AddChild(si);
        delete bmp;
        break;
      }

      RSE_IFilePic *fp = CreateFilePic(m_fns->Get(m_idx));

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

      if (fp)
      {
        if (fp->GetPicSize() > 0)
        {
          bmp = LICE_LoadJPGFromMemory(fp->GetPic(), fp->GetPicSize());

          if (bmp)
          {
            RSE_SingleImage *si = new RSE_SingleImage;
            si->m_img = new LICE_SysBitmap(200, 200);
            LICE_ScaledBlit(si->m_img, bmp, 0, 0, 200,
              200, 0.0, 0.0, (float)bmp->getWidth(),
              (float)bmp->getHeight(), 1.0f, LICE_BLIT_MODE_COPY);
            si->m_fn.Set(WDL_get_filepart(m_fns->Get(m_idx)));
            si->m_track = m_trk->Get()[m_idx];
            m_images.AddChild(si);
            delete bmp;
          }
        }
        delete fp;
      }
      break;
    }

    m_idx++;
  }

  return 1;
}

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

int RSE_PreviewArtworkWnd::OrganizeWindow()
{
  int margin = 20;
  int picw = 200;
  int pich = 200;
  int dstx = 0;
  int dsty = 0;
  int width = 0;

  RECT cr;
  m_images.GetPosition(&cr);
  dsty = cr.top;
  width = cr.right - cr.left;

  RECT r;

  for (int i = 0; i < m_images.GetNumChildren(); i++)
  {
    if (!i)
    {
      dstx = margin;
      dsty += margin;
    }
    else
    {
      dstx += picw + margin;
    }

    if (dstx + picw + margin > width && dstx > picw + margin)
    {
      dstx = margin;
      dsty += pich + margin;
    }

    r.left = dstx;
    r.top = dsty;
    r.right = r.left + picw;
    r.bottom = r.top + pich;

    m_images.EnumChildren(i)->SetPosition(&r);
  }

  dsty += pich - margin; // extra space

  return dsty;
}

void RSE_PreviewArtworkWnd::PreviewArtwork(WDL_PtrList<char> *files, WDL_TypedBuf<int> *tracks, HWND parent)
{
  if (!m_hwnd)
  {
    WDL_ASSERT(m_fns == NULL);
    WDL_ASSERT(m_trk == NULL);
    WDL_ASSERT(parent);
    WDL_ASSERT(files);
    WDL_ASSERT(tracks);
    m_fns = files;
    m_trk = tracks;

    CreateDialogParam(g_inst,
      MAKEINTRESOURCE(IDD_PREVIEW_ARTWORK),
      parent, RSE_PreviewArtworkWnd::ST_PreviewArtworkWndProc,
      (LPARAM)this);
    ShowWindow(m_hwnd, SW_SHOWDEFAULT);
  }
  else
  {
    if (files)
    {
      files->Empty(true, free);
      delete files;
    }

    if (tracks)
    {
      delete tracks;
    }

    SetFocus(m_hwnd);
  }
}

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

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

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

WDL_DLGRET RSE_PreviewArtworkWnd::PreviewArtworkWndProc(UINT msg, WPARAM wparam, LPARAM lparam)
{
#ifdef _WIN32
  if (g_scroll_message && msg == g_scroll_message)
  {
    msg=WM_MOUSEWHEEL;
    wparam<<=16; 
  }
#endif
  switch (msg)
  {
  case WM_INITDIALOG:
    {
      InitializeCoolSB(m_hwnd);

      m_images.SetRealParent(m_hwnd);

      int x = g_ini_file->read_int("preview_artwork_wnd_x", 50, EUTERPE_NAME);
      int y = g_ini_file->read_int("preview_artwork_wnd_y", 50, EUTERPE_NAME);
      int w = g_ini_file->read_int("preview_artwork_wnd_w", 800, EUTERPE_NAME);
      int h = g_ini_file->read_int("preview_artwork_wnd_h", 600, EUTERPE_NAME);
      SetWindowPos(m_hwnd, NULL, x, y, w, h, SWP_NOZORDER|SWP_NOACTIVATE);

      RECT r;
      GetWindowRect(m_hwnd, &r);
      ScreenToClient(m_hwnd, (LPPOINT)&r);
      ScreenToClient(m_hwnd, ((LPPOINT)&r)+1);

      SCROLLINFO si = 
      {
        sizeof(si),
        SIF_PAGE | SIF_POS | SIF_RANGE,
        0,
        r.bottom - r.top,
        r.bottom, //pich + margin,
        0,
      };

      CoolSB_SetScrollInfo(m_hwnd, SB_VERT, &si, TRUE);

      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_PREVIEW, RSE_LOAD_PREVIEW_MS, NULL);
    }
    break;
  case WM_TIMER:
    {
      if (wparam == RSE_LOAD_PREVIEW)
      {
        char txt[128];
        GetWindowText(m_hwnd, txt, sizeof(txt));

        if (m_idx < m_fns->GetSize())
        {
          InvalidateRect(m_hwnd, NULL, FALSE);
        }
        else if (strcmp(txt, "Preview artwork"))
        {
          InvalidateRect(m_hwnd, NULL, FALSE);
          SetWindowText(m_hwnd, "Preview artwork");
        }

        if (GetFocus() != m_hwnd)
        {
          DestroyWindow(m_hwnd);
        }
      }
    }
    break;
  case WM_ERASEBKGND:
    {
      //return 1;
    }
    break;
  case WM_PAINT:
    {
      RECT r;
      GetClientRect(m_hwnd, &r);

      r.bottom = OrganizeWindow();

      RECT rr;
      int pos = CoolSB_GetScrollPos(m_hwnd, SB_VERT);
      for (int i = 0; i < m_images.GetNumChildren(); i++)
      {
        m_images.EnumChildren(i)->GetPosition(&rr);
        rr.top -= pos;
        rr.bottom -= pos;
        m_images.EnumChildren(i)->SetPosition(&rr);
      }

      m_images.SetPosition(&r);

      SCROLLINFO si =
      {
        sizeof(si),
        SIF_PAGE|SIF_POS|SIF_RANGE,
        0,
        r.bottom,
        200,
        pos,
      };
      CoolSB_SetScrollInfo(m_hwnd, SB_VERT, &si, TRUE);

      m_painter.PaintBegin(m_hwnd, RGB(0, 0, 0)); // Gainsboro
      m_painter.PaintVirtWnd(&m_images);
      m_painter.PaintEnd();
    }
    break;
  case WM_SIZE:
    {
      if (wparam != SIZE_MINIMIZED)
      {
        InvalidateRect(m_hwnd, NULL, FALSE);
      }
    }
    break;
  case WM_SYSCOMMAND:
    {
      if (LOWORD(wparam) == SC_CLOSE)
      {
        DestroyWindow(m_hwnd);
      }
    }
    break;
  case WM_COMMAND:
    {
      switch(LOWORD(wparam))
      {
      case IDCANCEL:
        {
          DestroyWindow(m_hwnd);
        }
        break;
      }
    }
    break;
  case WM_DESTROY:
    {
      KillTimer(m_hwnd, RSE_LOAD_PREVIEW);

      m_kill_thread = true;

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

      m_idx = 0;

      RECT r;
      GetWindowRect(m_hwnd, &r);
      g_ini_file->write_int("preview_artwork_wnd_x", r.left, EUTERPE_NAME);
      g_ini_file->write_int("preview_artwork_wnd_y", r.top, EUTERPE_NAME);
      g_ini_file->write_int("preview_artwork_wnd_w", r.right - r.left, EUTERPE_NAME);
      g_ini_file->write_int("preview_artwork_wnd_h", r.bottom - r.top, EUTERPE_NAME);

      m_images.RemoveAllChildren(true);

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

      if (m_trk)
      {
        delete m_trk;
        m_trk = NULL;
      }

      UninitializeCoolSB(m_hwnd);

      m_hwnd = NULL;
    }
    break;
  case WM_LBUTTONDOWN:
    {
      SetFocus(m_hwnd);
      if (m_images.OnMouseDown(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)) > 0)
      {
        SetCapture(m_hwnd);
      }
    }
    break;
  case WM_LBUTTONUP:
    {
      if (GetCapture() == m_hwnd)
      {
        m_images.OnMouseUp(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
        ReleaseCapture();
      }
    }
    break;
  case WM_MOUSEMOVE:
    {
      //if (GetCapture() == m_hwnd)
      {
        m_images.OnMouseMove(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
      }
    }
    break;
#ifdef _WIN32
  case WM_GETDLGCODE:
    {
      if (wparam == VK_UP)
      {
        SendMessage(m_hwnd, WM_KEYDOWN, VK_UP, 0);
      }

      if (wparam == VK_DOWN)
      {
        SendMessage(m_hwnd, WM_KEYDOWN, VK_DOWN, 0);
      }
    }
    break;
#endif
  case WM_KEYDOWN:
    {
      switch (wparam)
      {
      case VK_HOME:
        {
          CoolSB_SetScrollPos(m_hwnd, SB_VERT, 0, TRUE);
          InvalidateRect(m_hwnd, NULL, FALSE);
        }
        break;
      case VK_END:
        {
          SCROLLINFO si = { sizeof(SCROLLINFO), SIF_POS|SIF_PAGE|SIF_RANGE|SIF_TRACKPOS, 0 };
          CoolSB_GetScrollInfo(m_hwnd, SB_VERT, &si);

          CoolSB_SetScrollPos(m_hwnd, SB_VERT, si.nMax, TRUE);
          InvalidateRect(m_hwnd, NULL, FALSE);
        }
        break;
      case VK_PRIOR:
        {
          SCROLLINFO si = { sizeof(SCROLLINFO), SIF_POS|SIF_PAGE|SIF_RANGE|SIF_TRACKPOS, 0 };
          CoolSB_GetScrollInfo(m_hwnd, SB_VERT, &si);

          int pos = si.nPos - si.nPage;
          if (pos < 0) pos = 0;

          CoolSB_SetScrollPos(m_hwnd, SB_VERT, pos, TRUE);
          InvalidateRect(m_hwnd, NULL, FALSE);
        }
        break;
      case VK_NEXT:
        {
          SCROLLINFO si = { sizeof(SCROLLINFO), SIF_POS|SIF_PAGE|SIF_RANGE|SIF_TRACKPOS, 0 };
          CoolSB_GetScrollInfo(m_hwnd, SB_VERT, &si);

          int pos = si.nPos + si.nPage;
          if (pos > si.nMax) pos = si.nMax;

          CoolSB_SetScrollPos(m_hwnd, SB_VERT, pos, TRUE);
          InvalidateRect(m_hwnd, NULL, FALSE);
        }
        break;
      case VK_UP:
        {
          SCROLLINFO si = { sizeof(SCROLLINFO), SIF_POS|SIF_PAGE|SIF_RANGE|SIF_TRACKPOS, 0 };
          CoolSB_GetScrollInfo(m_hwnd, SB_VERT, &si);

          int pos = si.nPos - 30;
          if (pos < 0) pos = 0;

          CoolSB_SetScrollPos(m_hwnd, SB_VERT, pos, TRUE);
          InvalidateRect(m_hwnd, NULL, FALSE);
        }
        break;
      case VK_DOWN:
        {
          SCROLLINFO si = { sizeof(SCROLLINFO), SIF_POS|SIF_PAGE|SIF_RANGE|SIF_TRACKPOS, 0 };
          CoolSB_GetScrollInfo(m_hwnd, SB_VERT, &si);

          int pos = si.nPos + 15;
          if (pos > si.nMax) pos = si.nMax;

          CoolSB_SetScrollPos(m_hwnd, SB_VERT, pos, TRUE);
          InvalidateRect(m_hwnd, NULL, FALSE);
        }
        break;
      }
    }
    break;
  case WM_MOUSEWHEEL:
    {
      POINT p = { GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
      ScreenToClient(m_hwnd, &p);
      if (!m_images.OnMouseWheel(p.x, p.y, -(short)HIWORD(wparam) / 120))
      {
        int l = (short)HIWORD(wparam);
        float pos = -l / 400.0f;

        SCROLLINFO si = { sizeof(SCROLLINFO), SIF_POS|SIF_PAGE|SIF_RANGE|SIF_TRACKPOS, 0 };
        CoolSB_GetScrollInfo(m_hwnd, SB_VERT, &si);

        pos *= (int)si.nPage;
        pos += si.nPos;

        const float mv = (float)(si.nMax - (int)si.nPage);
        if (pos > mv) pos = mv;
        if (pos < 0) pos = 0;

        CoolSB_SetScrollPos(m_hwnd, SB_VERT, (int)(pos + 0.5), TRUE);
        InvalidateRect(m_hwnd, NULL, FALSE);
      }
      return -1;
    }
    break;
  case WM_VSCROLL:
    {
      SCROLLINFO si = { sizeof(SCROLLINFO), SIF_POS|SIF_PAGE|SIF_RANGE|SIF_TRACKPOS, 0 };
      CoolSB_GetScrollInfo(m_hwnd, SB_VERT, &si);
      switch (LOWORD(wparam))
      {
      case SB_THUMBTRACK:
        si.nPos = si.nTrackPos;
        break;
      case SB_LINEUP:
        si.nPos -= 30;
        break;
      case SB_LINEDOWN:
        si.nPos += 30;
        break;
      case SB_PAGEUP:
        si.nPos -= (int)si.nPage;
        break;
      case SB_PAGEDOWN:
        si.nPos += (int)si.nPage;
        break;
      case SB_TOP:
        si.nPos = 0;
        break;
      case SB_BOTTOM:
        si.nPos = si.nMax-(int)si.nPage;
        break;
      }
      if (si.nPos > si.nMax-(int)si.nPage) si.nPos = si.nMax-(int)si.nPage;
      if (si.nPos < 0) si.nPos = 0;

      CoolSB_SetScrollPos(m_hwnd, SB_VERT, si.nPos, TRUE);

      InvalidateRect(m_hwnd, NULL, FALSE);
    }
    break;
  }

  return 0;
}
