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

#include "euterpe/main_wnd.h"

#include <string.h>

#include "euterpe/app_info.h"
#include "euterpe/plugin.h"
#include "euterpe/playlist_wnd.h"
#include "euterpe/transport_wnd.h"
#include "euterpe/theme.h"
#include "euterpe/icon_theme.h"
#include "euterpe/db2slider.h"

#include "WDL/filebrowse.h"
#include "WDL/dirscan.h"
#include "WDL/fileread.h"
#include "WDL/heapbuf.h"
#include "WDL/time_precise.h"
#include "WDL/jnetlib/jnetlib.h"

#define RSE_DISABLE_PLAYLIST_CONTROL 800
#define RSE_DISABLE_PLAYLIST_CONTROL_MS 50
#define RSE_MAIN_MENU_BAR 810
#define RSE_MAIN_MENU_BAR_MS 500
#define RSE_MAIN_TITLE_BAR 820
#define RSE_MAIN_TITLE_BAR_MS 90
#define RSE_CUT_COPY_PASTE_INFO 830
#define RSE_CUT_COPY_PASTE_INFO_MS 90
#define RSE_PROGRESS_WINDOW 840
#define RSE_PROGRESS_WINDOW_MS 50

unsigned WINAPI MainLoopThreadFunction(void *arg)
{
//#if defined(_WIN32)
//  CoInitializeEx(NULL, COINIT_MULTITHREADED);
//#endif

  while (!g_main_loop_done)
  {
    g_main_loop_mutex.Enter();
    while (!g_main_loop->Run());
    g_main_loop_mutex.Leave();

    Sleep(1);
  }

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

  return 0;
}

RSE_MainWnd::RSE_MainWnd()
  : m_hwnd(NULL)
  , m_title_capture(false)
  , m_x(0)
  , m_y(0)
  , m_w(0)
  , m_h(0)
  , m_cut_copy_paste_ms(0)
{}

RSE_MainWnd::~RSE_MainWnd()
{}

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

void RSE_MainWnd::ShowArtwork()
{
  if (!m_artwork_wnd.Handle())
  {
    CreateDialogParam(g_inst,
      MAKEINTRESOURCE(IDD_ARTWORK),
      m_hwnd, RSE_ArtworkWnd::ST_ArtworkWndProc,
      (LPARAM)&m_artwork_wnd);
    ShowWindow(m_artwork_wnd.Handle(), SW_SHOWNA);
    CheckMenuItem(GetMenu(m_hwnd), ID_OPTIONS_VIEWARTWORK, MF_CHECKED);
  }
  else
  {
    DestroyWindow(m_artwork_wnd.Handle());
    CheckMenuItem(GetMenu(m_hwnd), ID_OPTIONS_VIEWARTWORK, MF_UNCHECKED);
  }
}

void RSE_MainWnd::ShowKillRegionInfo(const char *info)
{
  m_cut_copy_paste_info.Set(info);
  m_cut_copy_paste_ms = 0;
  KillTimer(m_hwnd, RSE_MAIN_TITLE_BAR);
  SetTimer(m_hwnd, RSE_CUT_COPY_PASTE_INFO, RSE_CUT_COPY_PASTE_INFO_MS, NULL);
}

RSE_FileTasks *RSE_MainWnd::FileTask()
{
  return &m_file_task;
}

bool RSE_MainWnd::IsEngineOnline() const
{
  return g_main_loop_thread && g_audio_streamer;
}

void RSE_MainWnd::SetEngineOnline()
{
  WDL_FastString asn;

#if defined(_WIN32)
  asn.Set(g_ini_file->read_str("audio_system_name", "WaveOut", "preferences"));
#elif defined(__linux__)
  asn.Set(g_ini_file->read_str("audio_system_name", "PulseAudio", "preferences"));
#endif

  g_audio_streamer = CreateAudioStreamer(asn.Get());

  if (!g_audio_streamer)
  {
    g_ini_file->write_int("last_page", 2, "preferences");
    SendMessage(m_hwnd, WM_COMMAND, ID_PREFERENCES, 0);
    return;
  }

  g_main_loop_done = 0;
  unsigned int thread_id;
  g_main_loop_thread = (HANDLE)_beginthreadex(
    NULL, 0, MainLoopThreadFunction, 0, 0, &thread_id);

  SetThreadPriority(g_main_loop_thread, THREAD_PRIORITY_ABOVE_NORMAL);

  if (!g_audio_streamer)
  {
    WDL_FastString hw_error;
    hw_error.Set("Euterpe cannot find the audio system: ");
    hw_error.Append(asn.Get());
    //MessageBox(g_main_wnd->Handle(), hw_error.Get(), "Euterpe error", MB_OK);
  }
  else if (!g_audio_streamer->Open())
  {
    WDL_FastString hw_error;
    hw_error.Set("Euterpe cannot initialize the audio system: ");
    hw_error.Append(asn.Get());
    //MessageBox(g_main_wnd->Handle(), hw_error.Get(), "Euterpe error", MB_OK);
  }
}

void RSE_MainWnd::SetEngineOffline()
{
  if (!g_audio_streamer) return;

  g_main_loop_mutex.Enter();
  g_main_loop->Stop();
  g_main_loop->EmptyBufferQueue();
  g_main_loop_mutex.Leave();

  if (g_audio_streamer->IsRunning())
  {
    g_audio_streamer->Stop();
  }

  g_audio_streamer->Close();

  delete g_audio_streamer;
  g_audio_streamer = NULL;

  g_main_loop_done = 1;

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

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

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

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

WDL_DLGRET RSE_MainWnd::MainWndProc(UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch (msg)
  {
  case WM_INITDIALOG:
    {
      double wall0 = time_precise();

      m_file_task.SetParent(m_hwnd);

      if (IsStableRelease())
      {
        SetWindowText(m_hwnd, EUTERPE_NAME_MARKETING);
      }
      else
      {
        SetWindowText(m_hwnd, EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
      }

#if defined(_WIN32)
      SetClassLongPtr(m_hwnd, GCLP_HICON, (INT_PTR)LoadImage(g_inst,
        MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, 32, 32, LR_SHARED));

      SetClassLongPtr(m_hwnd, GCLP_HICONSM, (INT_PTR)LoadImage(g_inst,
        MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, 16, 16, LR_SHARED));
#endif

      g_keymap = new RSE_KeyboardMap;

      WDL_FastString theme_fn(g_module_path.Get());
      theme_fn.Append("themes" WDL_DIRCHAR_STR "mask.euterpe_theme");
      g_theme = new RSE_PackedTheme();

      if (!g_theme->LoadResource(theme_fn.Get()))
      {
        g_theme->UnloadResource();
      }
      else
      {
        g_icon_theme.LoadImages();
      }

      g_main_loop = new RSE_MainLoop();

      SetEngineOnline();
      wdl_log("main: online\n");

      RECT main_rect, playlist_rect, transport_rect;
      GetClientRect(m_hwnd, &main_rect);

      CreateDialogParam(g_inst, MAKEINTRESOURCE(IDD_TRANSPORT), m_hwnd,
        RSE_TransportWnd::ST_TransportWndProc, (LPARAM)&m_ts_wnd);
      GetWindowRect(m_ts_wnd.Handle(), &transport_rect);
      SetWindowPos(m_ts_wnd.Handle(), NULL, main_rect.left, main_rect.top,
        main_rect.right - main_rect.left, transport_rect.bottom - transport_rect.top,
        SWP_NOACTIVATE | SWP_NOZORDER);

      CreateDialogParam(g_inst, MAKEINTRESOURCE(IDD_PLAYLIST), m_hwnd,
        RSE_PlayListWnd::ST_PlayListWndProc, (LPARAM)&m_pl_wnd);
      GetWindowRect(m_pl_wnd.Handle(), &playlist_rect);
      SetWindowPos(m_pl_wnd.Handle(), NULL, main_rect.left,
        main_rect.top + (transport_rect.bottom - transport_rect.top),
        main_rect.right - main_rect.left,
        (main_rect.bottom - main_rect.top) - (transport_rect.bottom - transport_rect.top),
        SWP_NOACTIVATE | SWP_NOZORDER);

      m_resize.init(m_hwnd);
      m_resize.init_itemhwnd(m_ts_wnd.Handle(), 0.0f, 0.0f, 1.0f, 0.0f);
      m_resize.init_itemhwnd(m_pl_wnd.Handle(), 0.0f, 0.0f, 1.0f, 1.0f);

      m_main_menu = LoadMenu(g_inst, MAKEINTRESOURCE(IDR_MAIN_MENUBAR));
      SetMenu(m_hwnd, m_main_menu);

      m_x = g_ini_file->read_int("main_wnd_x", 50, EUTERPE_NAME);
      m_y = g_ini_file->read_int("main_wnd_y", 50, EUTERPE_NAME);
      m_w = g_ini_file->read_int("main_wnd_w", 1024, EUTERPE_NAME);
      m_h = g_ini_file->read_int("main_wnd_h", 768, EUTERPE_NAME);
      SetWindowPos(m_hwnd, NULL, m_x, m_y, m_w, m_h, SWP_NOACTIVATE);

      //EnableMenuItem(GetMenu(m_hwnd), ID_ADDURL, MF_GRAYED);
      //EnableMenuItem(GetMenu(m_hwnd), ID_PLAYURL, MF_GRAYED);

      WDL_FastString dbstr;
      dbstr.Set(g_ini_file->read_str("volume_db", "0.0", EUTERPE_NAME));
      g_main_loop_mutex.Enter();
      g_main_loop->SetVolume(atof(dbstr.Get()));
      g_main_loop_mutex.Leave();

      ShowWindow(m_ts_wnd.Handle(), SW_SHOW);
      ShowWindow(m_pl_wnd.Handle(), SW_SHOW);

      int maximized = g_ini_file->read_int("main_wnd_maximized", 0, EUTERPE_NAME);

      if (maximized)
      {
        ShowWindow(m_hwnd, SW_SHOWMAXIMIZED);
      }
      else
      {
        ShowWindow(m_hwnd, SW_SHOWNORMAL);
      }

      double wall1 = time_precise();

      if (!IsStableRelease())
      {
        const char warn_msg[] =
        {
          "Welcome to " EUTERPE_NAME_MARKETING " (the \"Software\").\n\n"
          "THIS IS AN UNSTABLE RELEASE STILL IN DEVELOPEMENT PHASE\n"
          "AND IS PROVIDED ON AN \"AS IS\" BASIS AND IS BELIEVED\n"
          "TO CONTAIN DEFECTS AND A PRIMARY PURPOSE OF THIS\n"
          "RELEASE IS TO OBTAIN FEEDBACK ON SOFTWARE PERFORMANCE\n"
          "AND THE IDENTIFICATION OF DEFECTS.\n\n"
          "IT IS ADVISED TO USE CAUTION AND NOT TO RELY IN ANY WAY\n"
          "ON THE CORRECT FUNCTIONING OF THIS RELEASE.\n\n"
          EUTERPE_NAME_MARKETING " version: " EUTERPE_NAKED_VERSION " (" EUTERPE_ARCH ")\n\n"
          "For the latest production release visit: " EUTERPE_WEBSITE_URL "\n\n"
          "Startup time: %.3f\n\n"
          EUTERPE_COPYRIGHT
        };

        WDL_FastString tmp;

        tmp.SetFormatted(1024, warn_msg, wall1 - wall0);

        MessageBox(m_hwnd, tmp.Get(), "Release for testing purposes", MB_OK);
      }

      int time_elapsed = g_ini_file->read_int("time_elapsed", 1, "preferences");

      if (time_elapsed)
      {
        CheckMenuItem(GetMenu(g_main_wnd->Handle()),
          ID_OPTIONS_TIMEREMAINING, MF_UNCHECKED);
        CheckMenuItem(GetMenu(g_main_wnd->Handle()),
          ID_OPTIONS_TIMEELAPSED, MF_CHECKED);
      }
      else
      {
        CheckMenuItem(GetMenu(g_main_wnd->Handle()),
          ID_OPTIONS_TIMEREMAINING, MF_CHECKED);
        CheckMenuItem(GetMenu(g_main_wnd->Handle()),
          ID_OPTIONS_TIMEELAPSED, MF_UNCHECKED);
      }

      int repeat_on = g_ini_file->read_int("repeat", 0, EUTERPE_NAME);
      int shuffle_on = g_ini_file->read_int("shuffle", 0, EUTERPE_NAME);

      if (repeat_on)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->ToggleRepeat();
        g_main_loop_mutex.Leave();
      }

      if (shuffle_on)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->ToggleShuffle();
        g_main_loop_mutex.Leave();
      }

      int curtrack = g_ini_file->read_int("current_track", 0, EUTERPE_NAME);
      g_main_loop_mutex.Enter();
      g_main_loop->SetCurrentTrack(curtrack);
      g_main_loop_mutex.Leave();

      int is_time_elapsed = g_ini_file->read_int("time_elapsed", 1, EUTERPE_NAME);

      if (is_time_elapsed)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->SetTimeElapsed(true);
        g_main_loop_mutex.Leave();
      }
      else
      {
        g_main_loop_mutex.Enter();
        g_main_loop->SetTimeElapsed(false);
        g_main_loop_mutex.Leave();
      }

      SetTimer(m_hwnd, RSE_MAIN_MENU_BAR, RSE_MAIN_MENU_BAR_MS, NULL);
      SetTimer(m_hwnd, RSE_MAIN_TITLE_BAR, RSE_MAIN_TITLE_BAR_MS, NULL);
    }
    break;

  case WM_COPYDATA:
    {
      // 1: add
      // 2: addnew
      // 3: pause
      // 4: previous
      // 5: next
      // 6: play
      // 7: stop
      // 8: exit
      // 9: show
      // 10: hide
      COPYDATASTRUCT *cds = (COPYDATASTRUCT *)lparam;
      if (cds->dwData == 1)
      {
        const char *p = (const char *)cds->lpData;
        m_pl_wnd.AddItem(p);
      }
      else if (cds->dwData == 2)
      {
        m_pl_wnd.RemoveAll();
      }
      else if (cds->dwData == 3)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->Pause();
        g_main_loop_mutex.Leave();
      }
      else if (cds->dwData == 4)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->Previous();
        g_main_loop_mutex.Leave();
      }
      else if (cds->dwData == 5)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->Next();
        g_main_loop_mutex.Leave();
      }
      else if (cds->dwData == 6)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->Play();
        g_main_loop_mutex.Leave();
      }
      else if (cds->dwData == 7)
      {
        g_main_loop_mutex.Enter();
        g_main_loop->Stop();
        g_main_loop_mutex.Leave();
      }
      else if (cds->dwData == 8)
      {
        SendMessage(m_hwnd, WM_COMMAND, ID_EXIT, 0);
      }
      else if (cds->dwData == 9)
      {
        ShowWindow(m_hwnd, SW_SHOW);
      }
      else if (cds->dwData == 10)
      {
        ShowWindow(m_hwnd, SW_HIDE);
      }
    }
    break;

  case WM_TIMER:
    {
      if (wparam == RSE_DISABLE_PLAYLIST_CONTROL)
      {
        if (!GetCapture() || !m_title_capture)
        {
          KillTimer(m_hwnd, RSE_DISABLE_PLAYLIST_CONTROL);
          SendMessage(GetDlgItem(m_pl_wnd.Handle(), IDC_LIST1), WM_SETREDRAW, TRUE, 0);
        }
      }

      if (wparam == RSE_MAIN_MENU_BAR)
      {
        WDL_HeapBuf *kr = NULL;

        g_main_loop_mutex.Enter();
        bool is_muted = g_main_loop->IsMuted();
        bool is_playing = g_main_loop->IsPlaying();
        bool is_paused = g_main_loop->IsPaused();
        bool is_repeat_on = g_main_loop->IsRepeatOn();
        bool is_shuffle_on = g_main_loop->IsShuffleOn();
        bool is_time_elapsed = g_main_loop->IsTimeElapsed();
        bool has_stop_after_current = g_main_loop->HasStopAfterCurrent();
        g_main_loop->GetKillRegion(&kr);
        g_main_loop_mutex.Leave();

        if (is_muted)
        {
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_PLAY_TOGGLEMUTE, MF_CHECKED);
        }
        else
        {
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_PLAY_TOGGLEMUTE, MF_UNCHECKED);
        }

        if (is_playing)
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_PLAY, MF_CHECKED);
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_STOP, MF_UNCHECKED);
        }
        else
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_PLAY, MF_UNCHECKED);
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_STOP, MF_CHECKED);
        }

        if (is_paused)
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_PAUSE, MF_CHECKED);
        }
        else
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_PAUSE, MF_UNCHECKED);
        }

        if (is_repeat_on)
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_REPEAT, MF_CHECKED);
        }
        else
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_REPEAT, MF_UNCHECKED);
        }

        if (is_shuffle_on)
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_SHUFFLE, MF_CHECKED);
        }
        else
        {
          CheckMenuItem(GetMenu(m_hwnd), ID_PLAY_SHUFFLE, MF_UNCHECKED);
        }

        if (is_time_elapsed)
        {
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_OPTIONS_TIMEREMAINING, MF_UNCHECKED);
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_OPTIONS_TIMEELAPSED, MF_CHECKED);
        }
        else
        {
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_OPTIONS_TIMEREMAINING, MF_CHECKED);
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_OPTIONS_TIMEELAPSED, MF_UNCHECKED);
        }

        if (has_stop_after_current)
        {
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_PLAY_STOPAFTERCURRENT, MF_CHECKED);
        }
        else
        {
          CheckMenuItem(GetMenu(g_main_wnd->Handle()),
            ID_PLAY_STOPAFTERCURRENT, MF_UNCHECKED);
        }

        if (kr)
        {
          EnableMenuItem(GetMenu(m_hwnd), ID_EDIT_PASTE_TRACKS, MF_ENABLED);
        }
        else
        {
          //EnableMenuItem(GetMenu(m_hwnd), ID_EDIT_PASTE_TRACKS, MF_DISABLED);
          EnableMenuItem(GetMenu(m_hwnd), ID_EDIT_PASTE_TRACKS, MF_GRAYED);
        }

        if (!is_playing && !GetActiveWindow() && IsEngineOnline())
        {
          SendMessage(m_hwnd, WM_ACTIVATEAPP, FALSE, 0);
          wdl_log("main timer: offline\n");
        }
      }

      if (wparam == RSE_MAIN_TITLE_BAR)
      {
        g_main_loop_mutex.Enter();
        int qtracks = g_main_loop->TracksInQueue();
        g_main_loop_mutex.Leave();

        if (!GetCapture())
        {
          if (qtracks)
          {
            if (IsStableRelease())
            {
              m_titstr.SetFormatted(128, "%s ", EUTERPE_NAME_MARKETING);
            }
            else
            {
              m_titstr.SetFormatted(128, "%s ", EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
            }

            g_main_loop_mutex.Enter();
            int next = g_main_loop->NextInQueue();
            g_main_loop_mutex.Leave();

            m_titstr.AppendFormatted(128, "| Next: %d [%d]", next + 1, qtracks);

            SetWindowText(g_main_wnd->Handle(), m_titstr.Get());
          }
          else
          {
            if (IsStableRelease())
            {
              SetWindowText(g_main_wnd->Handle(), EUTERPE_NAME_MARKETING);
            }
            else
            {
              SetWindowText(g_main_wnd->Handle(), EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
            }
          }
        }
      }

      if (wparam == RSE_CUT_COPY_PASTE_INFO)
      {
        m_cut_copy_paste_ms += RSE_CUT_COPY_PASTE_INFO_MS;

        if (m_cut_copy_paste_ms < 500)
        {
          if (IsStableRelease())
          {
            m_titstr.SetFormatted(128, "%s ", EUTERPE_NAME_MARKETING);
          }
          else
          {
            m_titstr.SetFormatted(128, "%s ", EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
          }

          m_titstr.Append(m_cut_copy_paste_info.Get());

          SetWindowText(g_main_wnd->Handle(), m_titstr.Get());
        }
        else
        {
          KillTimer(m_hwnd, RSE_CUT_COPY_PASTE_INFO);
          SetTimer(m_hwnd, RSE_MAIN_TITLE_BAR, RSE_MAIN_TITLE_BAR_MS, NULL);
        }
      }

      if (wparam == RSE_PROGRESS_WINDOW)
      {
        if (m_file_task.WantProgressWindow())
        {
          if (!m_ft_prog_wnd.Handle())
          {
            DialogBoxParam(g_inst, MAKEINTRESOURCE(IDD_FILE_TASKS_PROGRESS),
              m_hwnd, RSE_FileTasksProgressWnd::ST_FileTasksProgressWndProc, (LPARAM)&m_ft_prog_wnd);
            ShowWindow(m_ft_prog_wnd.Handle(), SW_SHOW);
          }
        }
        else
        {
          if (m_ft_prog_wnd.Handle())
          {
            EndDialog(m_ft_prog_wnd.Handle(), 0);
            KillTimer(m_hwnd, RSE_PROGRESS_WINDOW);
          }
        }
      }
    }
    break;

  case WM_NCLBUTTONDOWN:
    {
      m_title_capture = true;
      SetTimer(m_hwnd, RSE_DISABLE_PLAYLIST_CONTROL, RSE_DISABLE_PLAYLIST_CONTROL_MS, NULL);
      SendMessage(GetDlgItem(m_pl_wnd.Handle(), IDC_LIST1), WM_SETREDRAW, FALSE, 0);
    }
    break;

  case WM_ERASEBKGND:
    {
      //return TRUE;
      return 1;
    }
    break;

  case WM_ACTIVATEAPP:
    {
      g_main_loop_mutex.Enter();
      bool is_playing = g_main_loop->IsPlaying();
      g_main_loop_mutex.Leave();

      if (!is_playing && !m_pr_wnd.Handle())
      {
        if (wparam == TRUE) // activated
        {
          if (!IsEngineOnline())
          {
            SetEngineOnline();
            wdl_log("activateapp: online\n");
          }

          SetTimer(m_ts_wnd.Handle(),
            RSE_UPDATE_TRANSPORT_CONTROLS,
            RSE_UPDATE_TRANSPORT_CONTROLS_MS,
            NULL);
          SetTimer(m_hwnd, RSE_MAIN_TITLE_BAR,
            RSE_MAIN_TITLE_BAR_MS, NULL);
          SetTimer(m_hwnd, RSE_MAIN_MENU_BAR,
            RSE_MAIN_MENU_BAR_MS, NULL);

          g_main_loop_mutex.Enter();
          int qtracks = g_main_loop->TracksInQueue();
          g_main_loop_mutex.Leave();

          if (qtracks)
          {
            if (IsStableRelease())
            {
              m_titstr.SetFormatted(128, "%s ", EUTERPE_NAME_MARKETING);
            }
            else
            {
              m_titstr.SetFormatted(128, "%s ", EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
            }

            g_main_loop_mutex.Enter();
            int next = g_main_loop->NextInQueue();
            g_main_loop_mutex.Leave();

            m_titstr.AppendFormatted(128, "| Next: %d [%d]", next + 1, qtracks);

            SetWindowText(m_hwnd, m_titstr.Get());
          }
          else
          {
            if (IsStableRelease())
            {
              SetWindowText(m_hwnd, EUTERPE_NAME_MARKETING);
            }
            else
            {
              SetWindowText(m_hwnd, EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
            }
          }
        }
        else if (wparam == FALSE) // deactivated
        {
          if (IsEngineOnline())
          {
            SetEngineOffline();
            wdl_log("activateapp: offline\n");

            KillTimer(m_ts_wnd.Handle(),
              RSE_UPDATE_TRANSPORT_CONTROLS);
            KillTimer(m_hwnd, RSE_MAIN_TITLE_BAR);
            KillTimer(m_hwnd, RSE_MAIN_MENU_BAR);
          }

          if (IsStableRelease())
          {
            m_titstr.SetFormatted(128, "%s ", EUTERPE_NAME_MARKETING);
          }
          else
          {
            m_titstr.SetFormatted(128, "%s ", EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
          }

          m_titstr.Append("| Offline");

          SetWindowText(m_hwnd, m_titstr.Get());
        }
      }

      return 0;
    }
    break;

  case WM_SYSCOMMAND:
    {
      if (wparam == SC_CLOSE)
      {
        SendMessage(m_hwnd, WM_COMMAND, ID_EXIT, 0);
      }
    }
    break;

  case WM_CLOSE:
    {
#if defined(__linux__)
      has_requested_quit = true;
#endif
      return 0;
    }
    break;

  case WM_PAINT:
    {
      PAINTSTRUCT ps;
      //int cx = GetSystemMetrics(SM_CXSCREEN);
      //int cy = GetSystemMetrics(SM_CYSCREEN);

      HDC dc = BeginPaint(m_hwnd, &ps);
      //BitBlt(dc, 0, 0, cx, cy, dc, 0, 0, SRCCOPY);
      EndPaint(m_hwnd, &ps);

      //m_painter.PaintBegin(m_hwnd, RGB(255, 255, 255));

      //m_painter.PaintEnd();
    }
    break;
  case WM_SIZE:
    {
      if (wparam != SIZE_MINIMIZED)
      {
        m_title_capture = false;
        m_resize.onResize();
      }

      if (wparam != SIZE_MINIMIZED && wparam != SIZE_MAXIMIZED)
      {
        RECT r;
        GetWindowRect(m_hwnd, &r);

        m_x = r.left;
        m_y = r.top;
        m_w = r.right - r.left;
        m_h = r.bottom - r.top;
      }
    }
    break;

  case WM_MOVE:
    {
      int xpos = (int)(short) LOWORD(lparam); // horizontal position
      int ypos = (int)(short) HIWORD(lparam); // vertical position

      if (xpos >= 0 && ypos >= 0 && GetCapture() == m_hwnd)
      {
        RECT r;
        GetWindowRect(m_hwnd, &r);

        m_x = r.left;
        m_y = r.top;
        m_w = r.right - r.left;
        m_h = r.bottom - r.top;
      }
    }
    break;
  case WM_DESTROY:
    {
      KillTimer(m_hwnd, RSE_MAIN_MENU_BAR);
      KillTimer(m_hwnd, RSE_MAIN_TITLE_BAR);

      g_main_loop_mutex.Enter();
      int curtrack = g_main_loop->GetCurrentTrack();
      bool is_repeat_on = g_main_loop->IsRepeatOn();
      bool is_shuffle_on = g_main_loop->IsShuffleOn();
      bool is_time_elapsed = g_main_loop->IsTimeElapsed();
      g_main_loop_mutex.Leave();

      g_ini_file->write_int("current_track", curtrack, EUTERPE_NAME);

      if (is_repeat_on)
      {
        g_ini_file->write_int("repeat", 1, EUTERPE_NAME);
      }
      else
      {
        g_ini_file->write_int("repeat", 0, EUTERPE_NAME);
      }

      if (is_shuffle_on)
      {
        g_ini_file->write_int("shuffle", 1, EUTERPE_NAME);
      }
      else
      {
        g_ini_file->write_int("shuffle", 0, EUTERPE_NAME);
      }

      if (is_time_elapsed)
      {
        g_ini_file->write_int("time_elapsed", 1, EUTERPE_NAME);
      }
      else
      {
        g_ini_file->write_int("time_elapsed", 0, EUTERPE_NAME);
      }

#if defined(_WIN32)
      WINDOWPLACEMENT wp = { sizeof(wp) };
      GetWindowPlacement(m_hwnd, &wp);

      //if (wp.showCmd != SW_MINIMIZE && wp.showCmd != SW_SHOWMINIMIZED &&
      //  wp.showCmd != SW_MAXIMIZE && wp.showCmd != SW_SHOWMAXIMIZED)
      //{
      //  RECT r;
      //  GetWindowRect(m_hwnd, &r);

      //  g_ini_file->write_int("main_wnd_x", r.left, EUTERPE_NAME);
      //  g_ini_file->write_int("main_wnd_y", r.top, EUTERPE_NAME);
      //  g_ini_file->write_int("main_wnd_w", r.right - r.left, EUTERPE_NAME);
      //  g_ini_file->write_int("main_wnd_h", r.bottom - r.top, EUTERPE_NAME);
      //}
      g_ini_file->write_int("main_wnd_x", m_x, EUTERPE_NAME);
      g_ini_file->write_int("main_wnd_y", m_y, EUTERPE_NAME);
      g_ini_file->write_int("main_wnd_w", m_w, EUTERPE_NAME);
      g_ini_file->write_int("main_wnd_h", m_h, EUTERPE_NAME);

      if (wp.showCmd == SW_SHOWMAXIMIZED)
      {
        g_ini_file->write_int("main_wnd_maximized", 1, EUTERPE_NAME);
      }
      else
      {
        g_ini_file->write_int("main_wnd_maximized", 0, EUTERPE_NAME);
      }
#else
      RECT r;
      GetWindowRect(m_hwnd, &r);
      if (r.left >= 0 && r.top >= 0)
      {
        g_ini_file->write_int("main_wnd_x", r.left, EUTERPE_NAME);
        g_ini_file->write_int("main_wnd_y", r.top, EUTERPE_NAME);
        g_ini_file->write_int("main_wnd_w", r.right - r.left, EUTERPE_NAME);
        g_ini_file->write_int("main_wnd_h", r.bottom - r.top, EUTERPE_NAME);
      }
      //g_ini_file->write_int("main_wnd_x", m_x, EUTERPE_NAME);
      //g_ini_file->write_int("main_wnd_y", m_y, EUTERPE_NAME);
      //g_ini_file->write_int("main_wnd_w", m_w, EUTERPE_NAME);
      //g_ini_file->write_int("main_wnd_h", m_h, EUTERPE_NAME);
#endif

      g_main_loop_mutex.Enter();
      double db = g_main_loop->GetVolume();
      bool is_playing = g_main_loop->IsPlaying();

      if (is_playing)
      {
        g_main_loop->Stop();
      }
      g_main_loop_mutex.Leave();

      WDL_FastString dbstr;
      dbstr.SetFormatted(64, "%0.2f", db);
      g_ini_file->write_str("volume_db", dbstr.Get(), EUTERPE_NAME);

      if (IsEngineOnline())
      {
        SetEngineOffline();
        wdl_log("main: offline\n");
      }

      delete g_main_loop;
      delete g_keymap;
      delete g_theme;

      DestroyWindow(m_pl_wnd.Handle());
      DestroyWindow(m_ts_wnd.Handle());

      //DestroyMenu(m_main_menu);

#if defined(_WIN32)
      PostQuitMessage(0);
#elif defined(__APPLE__)
      SWELL_PostQuitMessage(m_hwnd);
#endif

      m_hwnd = NULL;

#if defined(__linux__)
      has_requested_quit = true;
#endif
      return 0;
    }
    break;

  case WM_COMMAND:
    {
      switch (LOWORD(wparam))
      {
      case IDOK:
      case IDNO:
      case IDCANCEL:
        {

        }
        break;
      case ID_PLAYFOLDER:
        SetTimer(m_hwnd, RSE_PROGRESS_WINDOW,
          RSE_PROGRESS_WINDOW_MS, NULL);
        m_file_task.PlayFolder();
        break;
      case ID_PLAYFILES:
        SetTimer(m_hwnd, RSE_PROGRESS_WINDOW,
          RSE_PROGRESS_WINDOW_MS, NULL);
        m_file_task.PlayFiles();
        break;
      case ID_ADDFOLDER:
        SetTimer(m_hwnd, RSE_PROGRESS_WINDOW,
          RSE_PROGRESS_WINDOW_MS, NULL);
        m_file_task.AddFolder();
        break;
      case ID_ADDFILES:
        SetTimer(m_hwnd, RSE_PROGRESS_WINDOW,
          RSE_PROGRESS_WINDOW_MS, NULL);
        m_file_task.AddFiles();
        break;
      case ID_FILE_LOADPLAYLIST:
        SetTimer(m_hwnd, RSE_PROGRESS_WINDOW,
          RSE_PROGRESS_WINDOW_MS, NULL);
        m_file_task.LoadPlayList();
        break;
      case ID_FILE_SAVEPLAYLIST:
        m_file_task.SavePlayList();
        break;
      case ID_PREFERENCES:
        {
          if (!m_pr_wnd.Handle())
          {
            CreateDialogParam(g_inst, MAKEINTRESOURCE(IDD_PREFERENCES),
              m_hwnd, RSE_PreferencesWnd::ST_PreferencesWndProc, (LPARAM)&m_pr_wnd);
            ShowWindow(m_pr_wnd.Handle(), SW_SHOW);
          }
          else
          {
            SetFocus(m_pr_wnd.Handle());
          }
        }
        break;
      case ID_HELP_LICENSE:
        {
          if (!m_lic_wnd.Handle())
          {
            CreateDialogParam(g_inst,
              MAKEINTRESOURCE(IDD_LICENSE),
              m_hwnd, RSE_LicenseWnd::ST_LicenseWndProc,
              (LPARAM)&m_lic_wnd);
            ShowWindow(m_lic_wnd.Handle(), SW_SHOW);
          }
          else
          {
            SetFocus(m_lic_wnd.Handle());
          }
        }
        break;
      case ID_HELP_ABOUTEUTERPE:
        {
          if (!m_about_wnd.Handle())
          {
            CreateDialogParam(g_inst,
              MAKEINTRESOURCE(IDD_ABOUT),
              m_hwnd, RSE_AboutWnd::ST_AboutWndProc,
              (LPARAM)&m_about_wnd);
            ShowWindow(m_about_wnd.Handle(), SW_SHOW);
          }
          else
          {
            SetFocus(m_about_wnd.Handle());
          }
        }
        break;
      case ID_HELP_CHANGELOG:
        {
          if (!m_cl_wnd.Handle())
          {
            CreateDialogParam(g_inst,
              MAKEINTRESOURCE(IDD_CHANGELOG),
              m_hwnd, RSE_ChangeLogWnd::ST_ChangelogWndProc,
              (LPARAM)&m_cl_wnd);
            ShowWindow(m_cl_wnd.Handle(), SW_SHOW);
          }
          else
          {
            SetFocus(m_cl_wnd.Handle());
          }
        }
        break;
      case ID_HELP_VERSION:
        {
          const char version_string[] =
          {
            EUTERPE_NAME_MARKETING " version: " EUTERPE_NAKED_VERSION " (" EUTERPE_ARCH ")\n\n"
            "Build timestamp: " EUTERPE_TIMESTAMP "\n"
            "Git commit: " EUTERPE_GIT_SHA
          };

          MessageBox(m_hwnd, version_string, "Version details", MB_OK);
        }
        break;
      case ID_HELP_TWITTER:
        {
          ShellExecute(m_hwnd, "", "https://twitter.com/grafmin", "", "", SW_SHOWNORMAL);
        }
        break;
      case ID_HELP_EUTERPEHOME:
        {
          ShellExecute(m_hwnd, "", EUTERPE_WEBSITE_URL, "", "", SW_SHOWNORMAL);
        }
        break;
      case ID_HELP_DOCUMENTATION:
        {
          WDL_FastString doc;

          doc.Set(g_module_path.Get());
          doc.Append("documentation.txt");

          ShellExecute(m_hwnd, "", "notepad.exe", doc.Get(), "", SW_SHOWNORMAL);
        }
        break;
      case ID_EXIT:
        DestroyWindow(m_hwnd);
        break;
      case ID_EDIT_FINDINPL:
        {
          if (!m_find_wnd.Handle())
          {
            CreateDialogParam(g_inst, MAKEINTRESOURCE(IDD_FIND),
              m_hwnd, RSE_FindWnd::ST_FindWndProc, (LPARAM)&m_find_wnd);
            ShowWindow(m_find_wnd.Handle(), SW_SHOW);
          }
          else
          {
            SetFocus(m_find_wnd.Handle());
          }
        }
        break;
      case ID_EDIT_CUT_TRACKS:
        m_pl_wnd.CutTracks();
        break;
      case ID_EDIT_COPY_TRACKS:
        m_pl_wnd.CopyTracks();
        break;
      case ID_EDIT_PASTE_TRACKS:
        m_pl_wnd.PasteTracks();
        break;
      case ID_EDIT_SORT:
        m_pl_wnd.SortMenu();
        break;
      case ID_EDIT_REVERSEPLAYLIST:
        m_pl_wnd.Reverse();
        break;
      case ID_EDIT_RANDOMIZEPLAYLIST:
        m_pl_wnd.Randomize();
        break;
      case ID_EDIT_SELECTCURRENT:
        m_pl_wnd.SelectCurrent();
        break;
      case ID_EDIT_SELECTALL:
        m_pl_wnd.SelectAll();
        break;
      case ID_EDIT_SELECTNONE:
        m_pl_wnd.SelectNone();
        break;
      case ID_EDIT_INVERTSELECTION:
        m_pl_wnd.InvertSelection();
        break;
      case ID_EDIT_REMOVESELECTED:
        m_pl_wnd.RemoveSelected();
        break;
      case ID_EDIT_CROPSELECTED:
        m_pl_wnd.CropSelected();
        break;
      case ID_EDIT_CLEARPLAYLIST:
        m_pl_wnd.RemoveAll();
        break;
      case ID_EDIT_REMOVEMISSINGFILES:
        m_pl_wnd.RemoveMissingFiles();
        break;
      case ID_EDIT_QUEUESELECTED:
        m_pl_wnd.AddToQueue();
        break;
      case ID_EDIT_REMOVEFROMQUEUE:
        m_pl_wnd.RemoveFromQueue();
        break;
      case ID_EDIT_CLEARQUEUE:
        m_pl_wnd.ClearQueue();
        break;
      case ID_EDIT_MOVEAFTERCURRENT:
        m_pl_wnd.MoveAfterCurrent();
        break;
      case ID_EDIT_MOVESELECTEDUP:
        m_pl_wnd.MoveUp();
        break;
      case ID_EDIT_MOVESELECTEDDOWN:
        m_pl_wnd.MoveDown();
        break;
      case ID_EDIT_REMOVEDUPLICATEENTRIES:
        m_pl_wnd.RemoveDuplicateEntries();
        break;
      case ID_PLAY_PAUSE:
        g_main_loop_mutex.Enter();
        g_main_loop->Pause();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_PREVIOUS:
        g_main_loop_mutex.Enter();
        g_main_loop->Previous();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_NEXT:
        g_main_loop_mutex.Enter();
        g_main_loop->Next();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_PLAY:
        g_main_loop_mutex.Enter();
        g_main_loop->Play();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_STOP:
        g_main_loop_mutex.Enter();
        g_main_loop->Stop();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_STOPAFTERCURRENT:
        g_main_loop_mutex.Enter();
        g_main_loop->StopAfterCurrent();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_BACK:
        {
          g_main_loop_mutex.Enter();
          double time = g_main_loop->GetRunningTime();
          time -= 5.0;
          g_main_loop->Seek(time);
          g_main_loop_mutex.Leave();
        }
        break;
      case ID_PLAY_FORWARD:
        {
          g_main_loop_mutex.Enter();
          double time = g_main_loop->GetRunningTime();
          time += 5.0;
          g_main_loop->Seek(time);
          g_main_loop_mutex.Leave();
        }
        break;
      case ID_PLAY_REPEAT:
        g_main_loop_mutex.Enter();
        g_main_loop->ToggleRepeat();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_SHUFFLE:
        g_main_loop_mutex.Enter();
        g_main_loop->ToggleShuffle();
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_VOLUMEUP:
        {
          g_main_loop_mutex.Enter();
          double db = g_main_loop->GetVolume();
          g_main_loop->SetVolume(db + 2.0);
          g_main_loop_mutex.Leave();
        }
        break;
      case ID_PLAY_VOLUMEDOWN:
        {
          g_main_loop_mutex.Enter();
          double db = g_main_loop->GetVolume();
          g_main_loop->SetVolume(db - 2.0);
          g_main_loop_mutex.Leave();
        }
        break;
      case ID_PLAY_VOLUME0DB:
        g_main_loop_mutex.Enter();
        g_main_loop->SetVolume(0.0);
        g_main_loop_mutex.Leave();
        break;
      case ID_PLAY_EXACTVOLUME:
        {
          if (!m_exactvol_wnd.Handle())
          {
            CreateDialogParam(g_inst,
              MAKEINTRESOURCE(IDD_EXACT_VOLUME),
              m_hwnd, RSE_ExactVolWnd::ST_ExactVolWndProc,
              (LPARAM)&m_exactvol_wnd);
            ShowWindow(m_exactvol_wnd.Handle(), SW_SHOW);
            //CheckMenuItem(GetMenu(m_hwnd), ID_OPTIONS_VIEWARTWORK, MF_CHECKED);
          }
          else
          {
            SetFocus(m_exactvol_wnd.Handle());
            //DestroyWindow(m_pl_artwork_wnd.Handle());
            //CheckMenuItem(GetMenu(m_hwnd), ID_OPTIONS_VIEWARTWORK, MF_UNCHECKED);
          }
        }
        break;
      case ID_PLAY_TOGGLEMUTE:
        g_main_loop_mutex.Enter();
        g_main_loop->ToggleMute();
        g_main_loop_mutex.Leave();
        break;
      case ID_OPTIONS_TIMEELAPSED:
        g_main_loop_mutex.Enter();
        g_main_loop->SetTimeElapsed(true);
        g_main_loop_mutex.Leave();
        break;
      case ID_OPTIONS_TIMEREMAINING:
        g_main_loop_mutex.Enter();
        g_main_loop->SetTimeElapsed(false);
        g_main_loop_mutex.Leave();
        break;
      case ID_OPTIONS_VIEWARTWORK:
        ShowArtwork();
        break;
      }
    }
    break;
  }

  return 0;
}

int RSE_MainWnd::ProcessMessage(MSG *msg)
{
  if (msg->hwnd == m_hwnd || IsChild(m_hwnd, msg->hwnd))
  {
    if (msg->wParam == VK_CONTROL &&
        (msg->message == WM_KEYDOWN ||
         msg->message == WM_KEYUP))
    {
      if (GetCapture() == m_hwnd)
      {
        //InvalidateRect(m_hwnd, NULL, FALSE);
      }
    }

    //if (msg->message == WM_MOUSEWHEEL)
    //{
    //  SendMessage(msg->hwnd, WM_MOUSEWHEEL, msg->wParam, msg->lParam);

    //  return 1;
    //}

    if (msg->message == WM_KEYDOWN || msg->message == WM_SYSKEYDOWN /* || msg->message == WM_CHAR */)
    {
      // todo: keyboard table
      //if (msg->wParam == 'A' || (msg->wParam == VK_INSERT && msg->message == WM_KEYDOWN))
      //{
      //  return 1;
      //}

      if (msg->lParam & FVIRTKEY)
      {
        if (msg->wParam == g_keymap->GetKey("ExactVol"))
        {
          if (g_keymap->MatchModifier("ExactVol"))
          {
            if (!GetCapture())
            {
              SendMessage(m_hwnd, WM_COMMAND, ID_PLAY_EXACTVOLUME, 0);
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("PL_Delete"))
        {
          if (g_keymap->MatchModifier("PL_Delete"))
          {
            if (!GetCapture())
            {
              m_pl_wnd.RemoveSelected();
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("PL_Crop"))
        {
          if (g_keymap->MatchModifier("PL_Crop"))
          {
            if (!GetCapture())
            {
              m_pl_wnd.CropSelected();
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("PL_Clear"))
        {
          if (g_keymap->MatchModifier("PL_Clear"))
          {
            if (!GetCapture())
            {
              m_pl_wnd.RemoveAll();
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("PL_RemoveMissingFiles"))
        {
          if (g_keymap->MatchModifier("PL_RemoveMissingFiles"))
          {
            if (!GetCapture())
            {
              m_pl_wnd.RemoveMissingFiles();
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("PL_RemoveDuplicateEntries"))
        {
          if (g_keymap->MatchModifier("PL_RemoveDuplicateEntries"))
          {
            if (!GetCapture())
            {
              m_pl_wnd.RemoveDuplicateEntries();
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("MoveUp"))
        {
          if (g_keymap->MatchModifier("MoveUp"))
          {
            if (!GetCapture())
            {
              m_pl_wnd.MoveUp();
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("MoveDown"))
        {
          if (g_keymap->MatchModifier("MoveDown"))
          {
            if (!GetCapture())
            {
              m_pl_wnd.MoveDown();
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("AboutEuterpe"))
        {
          if (g_keymap->MatchModifier("AboutEuterpe"))
          {
            if (!GetCapture())
            {
              SendMessage(m_hwnd, WM_COMMAND, ID_HELP_ABOUTEUTERPE, 0);
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("Changelog"))
        {
          if (g_keymap->MatchModifier("Changelog"))
          {
            if (!GetCapture())
            {
              SendMessage(m_hwnd, WM_COMMAND, ID_HELP_CHANGELOG, 0);
            }

            return 1;
          }
        }
        if (msg->wParam == g_keymap->GetKey("Version"))
        {
          if (g_keymap->MatchModifier("Version"))
          {
            if (!GetCapture())
            {
              SendMessage(m_hwnd, WM_COMMAND, ID_HELP_VERSION, 0);
            }

            return 1;
          }
        }
      } // FVIRTKEY

      if (msg->wParam == g_keymap->GetKey("EatEscape") && msg->message == WM_KEYDOWN)
      {
        if (g_keymap->MatchModifier("EatEscape"))
        {
          if (!GetCapture())
          {
            //Do nothing
          }

          if (g_playlist_wnd && g_playlist_wnd->Handle() == GetCapture())
          {
            ReleaseCapture();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("ExitEuterpe"))
      {
        if (g_keymap->MatchModifier("ExitEuterpe"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_DESTROY, 0, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PL_SortMenu"))
      {
        if (g_keymap->MatchModifier("PL_SortMenu"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.SortMenu();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PL_Reverse"))
      {
        if (g_keymap->MatchModifier("PL_Reverse"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.Reverse();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PL_Randomize"))
      {
        if (g_keymap->MatchModifier("PL_Randomize"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.Randomize();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("FindInPL"))
      {
        if (g_keymap->MatchModifier("FindInPL"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_EDIT_FINDINPL, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("CutTracks"))
      {
        if (g_keymap->MatchModifier("CutTracks"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_EDIT_CUT_TRACKS, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("CopyTracks"))
      {
        if (g_keymap->MatchModifier("CopyTracks"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_EDIT_COPY_TRACKS, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PasteTracks"))
      {
        if (g_keymap->MatchModifier("PasteTracks"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_EDIT_PASTE_TRACKS, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PL_SelectAll"))
      {
        if (g_keymap->MatchModifier("PL_SelectAll"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.SelectAll();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PL_SelectNone"))
      {
        if (g_keymap->MatchModifier("PL_SelectNone"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.SelectNone();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PL_InvertSelection"))
      {
        if (g_keymap->MatchModifier("PL_InvertSelection"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.InvertSelection();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PL_SelectCurrent"))
      {
        if (g_keymap->MatchModifier("PL_SelectCurrent"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.SelectCurrent();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("VolumeUp"))
      {
        if (g_keymap->MatchModifier("VolumeUp"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            double db = g_main_loop->GetVolume();
            g_main_loop->SetVolume(db + 2.0);
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("VolumeDown"))
      {
        if (g_keymap->MatchModifier("VolumeDown"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            double db = g_main_loop->GetVolume();
            g_main_loop->SetVolume(db - 2.0);
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("MaxVolume"))
      {
        if (g_keymap->MatchModifier("MaxVolume"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->SetVolume(0.0);
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("ToggleMute"))
      {
        if (g_keymap->MatchModifier("ToggleMute"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->ToggleMute();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Repeat"))
      {
        if (g_keymap->MatchModifier("Repeat"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->ToggleRepeat();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Shuffle"))
      {
        if (g_keymap->MatchModifier("Shuffle"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->ToggleShuffle();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("AddToQueue"))
      {
        if (g_keymap->MatchModifier("AddToQueue"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.AddToQueue();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("RemoveFromQueue"))
      {
        if (g_keymap->MatchModifier("RemoveFromQueue"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.RemoveFromQueue();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("ClearQueue"))
      {
        if (g_keymap->MatchModifier("ClearQueue"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.ClearQueue();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Pause"))
      {
        if (g_keymap->MatchModifier("Pause"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->Pause();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Previous"))
      {
        if (g_keymap->MatchModifier("Previous"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->Previous();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Rewind"))
      {
        if (g_keymap->MatchModifier("Rewind"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            double time = g_main_loop->GetRunningTime();
            time -= 5.0;
            g_main_loop->Seek(time);
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Next"))
      {
        if (g_keymap->MatchModifier("Next"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->Next();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Forward"))
      {
        if (g_keymap->MatchModifier("Forward"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            double time = g_main_loop->GetRunningTime();
            time += 5.0;
            g_main_loop->Seek(time);
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Play"))
      {
        if (g_keymap->MatchModifier("Play"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->Play();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Stop"))
      {
        if (g_keymap->MatchModifier("Stop"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->Stop();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("StopAfterCurrent"))
      {
        if (g_keymap->MatchModifier("StopAfterCurrent"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_PLAY_STOPAFTERCURRENT, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PlaySelected"))
      {
        if (g_keymap->MatchModifier("PlaySelected"))
        {
          if (!GetCapture())
          {
            m_pl_wnd.PlaySelected();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("ViewArtwork"))
      {
        if (g_keymap->MatchModifier("ViewArtwork"))
        {
          if (!GetCapture())
          {
            ShowArtwork();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PreviewArtwork"))
      {
        if (g_keymap->MatchModifier("PreviewArtwork"))
        {
          if (!GetCapture())
          {
            SendMessage(m_pl_wnd.Handle(), WM_COMMAND, ID_PL_PREVIEWARTWORK, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PlayFolder"))
      {
        if (g_keymap->MatchModifier("PlayFolder"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_PLAYFOLDER, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("PlayFiles"))
      {
        if (g_keymap->MatchModifier("PlayFiles"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_PLAYFILES, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("AddFolder"))
      {
        if (g_keymap->MatchModifier("AddFolder"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_ADDFOLDER, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("AddFiles"))
      {
        if (g_keymap->MatchModifier("AddFiles"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_ADDFILES, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("LoadPlayList"))
      {
        if (g_keymap->MatchModifier("LoadPlayList"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_FILE_LOADPLAYLIST, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("SavePlayList"))
      {
        if (g_keymap->MatchModifier("SavePlayList"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_FILE_SAVEPLAYLIST, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("TS_ToggleTimeDisplay"))
      {
        if (g_keymap->MatchModifier("TS_ToggleTimeDisplay"))
        {
          if (!GetCapture())
          {
            g_main_loop_mutex.Enter();
            g_main_loop->ToggleTime();
            g_main_loop_mutex.Leave();
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Preferences"))
      {
        if (g_keymap->MatchModifier("Preferences"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_PREFERENCES, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("MoveAfterCurrent"))
      {
        if (g_keymap->MatchModifier("MoveAfterCurrent"))
        {
          if (!GetCapture())
          {
            SendMessage(m_hwnd, WM_COMMAND, ID_EDIT_MOVEAFTERCURRENT, 0);
          }

          return 1;
        }
      }
      if (msg->wParam == g_keymap->GetKey("Properties"))
      {
        if (g_keymap->MatchModifier("Properties"))
        {
          if (!GetCapture())
          {
            SendMessage(g_playlist_wnd->Handle(), WM_COMMAND, ID_PL_PROPERTIES, 0);
          }

          return 1;
        }
      }
    }
  }

  return 0;
}

int RSE_MainWnd::RunMessageLoop()
{
#if defined(_WIN32)
  MSG msg = { 0, };

  for (;;)
  {
    int vvv = GetMessage(&msg, NULL, 0, 0);

    if (!vvv)
    {
      break;
    }

    if (vvv < 0)
    {
      Sleep(10);
      continue;
    }

    if (!msg.hwnd)
    {
      DispatchMessage(&msg);
      continue;
    }

    vvv = ProcessMessage(&msg);

    if (vvv > 0)
    {
      continue;
    }

    if (IsDialogMessage(m_hwnd, &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_pl_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_pl_wnd.PropertiesHandle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_pl_wnd.PreviewHandle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_pr_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_about_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_artwork_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_ft_prog_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_cl_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_lic_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_find_wnd.Handle(), &msg))
    {
      continue;
    }

    if (IsDialogMessage(m_exactvol_wnd.Handle(), &msg))
    {
      continue;
    }

    HWND parent = NULL;
    HWND temp = msg.hwnd;

    do
    {
      if (GetClassLong(temp, GCW_ATOM) == (INT)32770)
      {
        parent = temp;

        if (!(GetWindowLong(temp, GWL_STYLE) &WS_CHILD))
        {
          break;  // not a child, exit
        }
      }
    }
    while (temp = GetParent(temp));

    if (parent && IsDialogMessage(parent, &msg))
    {
      continue;
    }

    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  return (int)msg.wParam;
#else
  MSG msg = { 0, };

  for (;;)
  {
    int x = ProcessMessage(&msg);

    if (x == 0)
    {
      break;
    }
  }

  return msg.wParam;
#endif
}

#include "WDL/eel2/ns-eel.h"
#include "WDL/lice/lice.h"
#include "WDL/wingui/virtwnd-controls.h"
#include "WDL/wingui/scrollbar/coolscroll.h"

// an app should implement these
int WDL_STYLE_WantGlobalButtonBorders() { return 0; }
bool WDL_STYLE_WantGlobalButtonBackground(int *col) { return false; }
bool WDL_STYLE_GetBackgroundGradient(double *gradstart, double *gradslope) { return false; }
LICE_IBitmap *WDL_STYLE_GetSliderBitmap2(bool vert) { return NULL; }
int WDL_STYLE_AllowSliderMouseWheel(WDL_VWnd *vw, double *scale) { if (GetAsyncKeyState(VK_CONTROL)&0x8000) *scale /= 16.0; return 1; }
bool WDL_STYLE_AllowSliderClickOutsideHandle(WDL_VWnd *wv) { return true; }
int WDL_STYLE_GetSliderDynamicCenterPos() { return 500; }

// TO BE IMPLEMENTED BY APP:
// implemented by calling app, can return a LICE_IBitmap **img for "scrollbar"
void *GetIconThemePointer(const char *name)
{
  if (!strcmp(name, "scrollbar"))
  {}

  return NULL;
}

// can be a passthrough to GetSysColor()
int CoolSB_GetSysColor(HWND m_hwnd, int val)
{
  return GetSysColor(val);
}


#include <WDL/eel2/ns-eel.h>

void NSEEL_HOSTSTUB_EnterMutex()
{}

void NSEEL_HOSTSTUB_LeaveMutex()
{}
