/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <string>

#include "test_common.h"

#pragma warning(push)
#pragma warning(disable : 4127)    // conditional expression is constant
#pragma warning(disable : 4244)    // conversion from 'x' to 'y', possible loss of data
#pragma warning(disable : 4505)    // unreferenced local function has been removed
#pragma warning(disable : 4701)    // potentially uninitialized local variable used

#define NK_IMPLEMENTATION
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_ASSERT(expr) TEST_ASSERT(expr, "nuklear assertion failed")
#include "3rdparty/nuklear/nuklear.h"

#if defined(WIN32)

#define NK_GDI_IMPLEMENTATION
#include "3rdparty/nuklear/nuklear_gdi.h"

static LRESULT CALLBACK NuklearWndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  if(msg == WM_CLOSE)
  {
    DestroyWindow(wnd);
    return 0;
  }
  if(msg == WM_DESTROY)
  {
    PostQuitMessage(0);
    return 0;
  }

  if(nk_gdi_handle_event(wnd, msg, wparam, lparam))
    return 0;

  return DefWindowProcW(wnd, msg, wparam, lparam);
}

WNDCLASSW wc = {};
HWND wnd = NULL;
HDC dc = NULL;
GdiFont *font = NULL;

nk_context *NuklearInit(int width, int height, const char *title)
{
  wc.style = CS_DBLCLKS;
  wc.lpfnWndProc = NuklearWndProc;
  wc.hInstance = GetModuleHandleA(NULL);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.lpszClassName = L"NuklearWindowClass";
  RegisterClassW(&wc);

  int len = (int)strlen(title);

  int wsize = MultiByteToWideChar(CP_UTF8, 0, title, len, NULL, 0);
  WCHAR *wstr = (WCHAR *)_alloca(wsize * sizeof(wchar_t) + 2);
  wstr[wsize] = 0;
  MultiByteToWideChar(CP_UTF8, 0, title, len, wstr, wsize);

  RECT rect = {0, 0, width, height};
  AdjustWindowRectEx(&rect, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_VISIBLE, FALSE,
                     WS_EX_WINDOWEDGE);
  wnd = CreateWindowExW(
      WS_EX_WINDOWEDGE, wc.lpszClassName, wstr, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_VISIBLE,
      (GetSystemMetrics(SM_CXSCREEN) - (rect.right - rect.left)) / 2,
      (GetSystemMetrics(SM_CYSCREEN) - (rect.bottom - rect.top)) / 2, rect.right - rect.left,
      rect.bottom - rect.top, NULL, NULL, wc.hInstance, NULL);

  dc = GetDC(wnd);

  font = nk_gdifont_create("Arial", 14);
  return nk_gdi_init(font, dc, width, height);
}

bool NuklearTick(nk_context *ctx)
{
  MSG msg = {};
  UpdateWindow(wnd);

  nk_input_begin(ctx);
  // Check to see if any messages are waiting in the queue
  while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  {
    // Translate the message and dispatch it to WindowProc()
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  nk_input_end(ctx);

  if(!IsWindowVisible(wnd) || msg.message == WM_QUIT)
    return false;

  return true;
}

void NuklearRender()
{
  nk_gdi_render(nk_rgb(30, 30, 30));
}

void NuklearShutdown()
{
  nk_gdifont_del(font);
  ReleaseDC(wnd, dc);
  DestroyWindow(wnd);
  UnregisterClassW(wc.lpszClassName, wc.hInstance);
}

#else

#define NK_XLIB_IMPLEMENTATION
#include "3rdparty/nuklear/nuklear_xlib.h"

Display *dpy = NULL;
Colormap cmap = 0;
Window win = 0;
int screen = 0;
XFont *font = NULL;

nk_context *NuklearInit(int width, int height, const char *title)
{
  dpy = XOpenDisplay(NULL);
  if(!dpy)
    return NULL;
  Window root = DefaultRootWindow(dpy);
  screen = XDefaultScreen(dpy);
  Visual *vis = XDefaultVisual(dpy, screen);
  cmap = XCreateColormap(dpy, root, vis, AllocNone);

  XSetWindowAttributes swa = {};
  swa.colormap = cmap;
  swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPress | ButtonReleaseMask |
                   ButtonMotionMask | Button1MotionMask | Button3MotionMask | Button4MotionMask |
                   Button5MotionMask | PointerMotionMask | KeymapStateMask;
  win = XCreateWindow(dpy, root, 0, 0, width, height, 0, XDefaultDepth(dpy, screen), InputOutput,
                      vis, CWEventMask | CWColormap, &swa);

  XStoreName(dpy, win, title);
  XMapWindow(dpy, win);
  Atom wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
  XSetWMProtocols(dpy, win, &wm_delete_window, 1);

  /* GUI */
  font = nk_xfont_create(dpy, "fixed");
  return nk_xlib_init(font, dpy, screen, win, width, height);
}

bool NuklearTick(nk_context *ctx)
{
  XEvent evt;

  nk_input_begin(ctx);
  while(XPending(dpy))
  {
    XNextEvent(dpy, &evt);
    if(evt.type == ClientMessage)
      return false;
    if(XFilterEvent(&evt, win))
      continue;
    nk_xlib_handle_event(dpy, screen, win, &evt);
  }
  nk_input_end(ctx);

  return true;
}

void NuklearRender()
{
  XClearWindow(dpy, win);
  nk_xlib_render(win, nk_rgb(30, 30, 30));
  XFlush(dpy);
}

void NuklearShutdown()
{
  nk_xfont_del(dpy, font);
  nk_xlib_shutdown();
  XUnmapWindow(dpy, win);
  XFreeColormap(dpy, cmap);
  XDestroyWindow(dpy, win);
  XCloseDisplay(dpy);
}

#endif

// nuklear
#pragma warning(pop)

std::vector<TestMetadata> &test_list()
{
  static std::vector<TestMetadata> list;
  return list;
}

void RegisterTest(TestMetadata test)
{
  test_list().push_back(test);
}

int main(int argc, char **argv)
{
  std::vector<TestMetadata> &tests = test_list();

  std::sort(tests.begin(), tests.end());

  if(argc >= 2 && !strcmp(argv[1], "--list"))
  {
    for(const TestMetadata &test : tests)
      printf("%s (%s) - %s\n", test.Name, test.APIName(), test.Description);

    fflush(stdout);
    return 1;
  }

  if(tests.empty())
  {
    fprintf(stderr, "No tests registered\n");
    fflush(stderr);
    return 1;
  }

  std::string testchoice;

  if(argc >= 2)
  {
    testchoice = argv[1];
  }
  else
  {
    const int width = 400, height = 575;

    nk_context *ctx = NuklearInit(width, height, "RenderDoc Test Program");

    if(!ctx)
      return 1;

    int curtest = 0;
    bool allow[(int)TestAPI::Count] = {};
    const char *allow_names[] = {
        "D3D11", "Vulkan", "OpenGL", "D3D12",
    };

    for(size_t i = 0; i < ARRAY_COUNT(allow); i++)
      allow[i] = true;

    char name_filter[256] = {};

    static_assert(ARRAY_COUNT(allow) == ARRAY_COUNT(allow_names), "Mismatched array");

    while(NuklearTick(ctx))
    {
      if(nk_begin(ctx, "Demo", nk_rect(0, 0, (float)width, (float)height), NK_WINDOW_NO_SCROLLBAR))
      {
        nk_layout_row_dynamic(ctx, 100, 1);
        if(nk_group_begin(ctx, "Test Filter", NK_WINDOW_BORDER | NK_WINDOW_TITLE))
        {
          nk_layout_row_dynamic(ctx, 30, ARRAY_COUNT(allow_names) + 1);

          nk_label(ctx, "Filter tests:", NK_TEXT_LEFT);

          for(size_t i = 0; i < ARRAY_COUNT(allow); i++)
          {
            std::string text = allow_names[i];

            allow[i] = nk_check_label(ctx, text.c_str(), allow[i]) != 0;
          }

          nk_group_end(ctx);
        }

        nk_layout_row_dynamic(ctx, 270, 1);
        if(nk_group_begin(ctx, "Test", NK_WINDOW_BORDER | NK_WINDOW_TITLE))
        {
          nk_layout_row_begin(ctx, NK_STATIC, 20, 2);
          nk_layout_row_push(ctx, 60.0f);
          nk_label(ctx, "Test Filter:", NK_TEXT_ALIGN_MIDDLE | NK_TEXT_ALIGN_RIGHT);
          nk_layout_row_push(ctx, 280.0f);
          nk_edit_string_zero_terminated(ctx, NK_EDIT_FIELD, name_filter, 256, NULL);
          nk_layout_row_end(ctx);

          float prevSpacing = 0;
          std::swap(prevSpacing, ctx->style.window.spacing.y);

          nk_layout_row_dynamic(ctx, 20, 1);

          std::string lower_filter = strlower(name_filter);

          for(int i = 0; i < (int)tests.size(); i++)
          {
            std::string name = tests[i].QualifiedName();
            std::string lower_name = strlower(name);

            // apply filters
            if(!allow[(int)tests[i].API] ||
               (!lower_filter.empty() && !strstr(lower_name.c_str(), lower_filter.c_str())))
            {
              // if this was the selected test, unselect it. The next unfiltered test will grab it
              if(curtest == i)
                curtest = -1;

              continue;
            }

            // grab the current test, if none is selected (see above)
            if(curtest == -1)
              curtest = i;

            if(nk_select_label(ctx, name.c_str(), NK_TEXT_LEFT, curtest == i))
              curtest = i;
          }

          std::swap(prevSpacing, ctx->style.window.spacing.y);
          nk_group_end(ctx);
        }

        nk_layout_row_dynamic(ctx, 150, 1);

        TestMetadata &selected_test = tests[curtest >= 0 ? curtest : 0];

        if(nk_group_begin(ctx, "Test Information", NK_WINDOW_BORDER | NK_WINDOW_TITLE))
        {
          if(curtest >= 0)
          {
            nk_layout_row_begin(ctx, NK_DYNAMIC, 20, 2);
            nk_layout_row_push(ctx, 0.25f);
            nk_label(ctx, "Test name:", NK_TEXT_ALIGN_TOP | NK_TEXT_ALIGN_RIGHT);
            nk_layout_row_push(ctx, 0.75f);
            nk_label(ctx, selected_test.Name, NK_TEXT_ALIGN_TOP | NK_TEXT_ALIGN_LEFT);
            nk_layout_row_end(ctx);

            nk_layout_row_begin(ctx, NK_DYNAMIC, 20, 2);
            nk_layout_row_push(ctx, 0.25f);
            nk_label(ctx, "API:", NK_TEXT_ALIGN_TOP | NK_TEXT_ALIGN_RIGHT);
            nk_layout_row_push(ctx, 0.75f);
            nk_label(ctx, selected_test.APIName(), NK_TEXT_ALIGN_TOP | NK_TEXT_ALIGN_LEFT);
            nk_layout_row_end(ctx);

            nk_layout_row_begin(ctx, NK_DYNAMIC, 0, 2);
            nk_layout_row_push(ctx, 0.25f);
            nk_label(ctx, "Description:", NK_TEXT_ALIGN_TOP | NK_TEXT_ALIGN_RIGHT);
            nk_layout_row_push(ctx, 0.75f);
            nk_label_wrap(ctx, selected_test.Description);
            nk_layout_row_end(ctx);
          }
          else
          {
            nk_layout_row_begin(ctx, NK_DYNAMIC, 20, 1);
            nk_layout_row_push(ctx, 0.25f);
            nk_label(ctx, "No test selected", NK_TEXT_ALIGN_TOP | NK_TEXT_ALIGN_CENTERED);
            nk_layout_row_end(ctx);
          }

          nk_group_end(ctx);
        }

        nk_layout_row_dynamic(ctx, 30, 1);

        if(curtest >= 0)
        {
          if(nk_button_label(ctx, "Run"))
          {
            testchoice = selected_test.Name;
            break;
          }
        }
        else
        {
          nk_label(ctx, "No test selected", NK_TEXT_ALIGN_TOP | NK_TEXT_ALIGN_CENTERED);
        }
      }
      nk_end(ctx);

      NuklearRender();
    }

    NuklearShutdown();
  }

  if(testchoice.empty())
    return 0;

#if defined(WIN32)
  if(AllocConsole())
  {
    FILE *dummy = NULL;
    freopen_s(&dummy, "CONOUT$", "w", stdout);
    freopen_s(&dummy, "CONOUT$", "w", stderr);
  }
#endif

  //////////////////////////////////////////////////////////////
  // D3D11 tests
  //////////////////////////////////////////////////////////////

  for(const TestMetadata &test : tests)
  {
    if(testchoice == test.Name)
    {
      TEST_LOG("\n\n======\nRunning %s\n\n", test.Name);
      int ret = test.test->main(argc, argv);
      return ret;
    }
  }

  TEST_ERROR("%s is not a known test", argv[1]);

  return 2;
}

#if defined(WIN32)
int WINAPI wWinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine,
                    _In_ int nShowCmd)
{
  LPWSTR *wargv;
  int argc;

  if(AttachConsole(ATTACH_PARENT_PROCESS))
  {
    FILE *dummy = NULL;
    freopen_s(&dummy, "CONOUT$", "w", stdout);
    freopen_s(&dummy, "CONOUT$", "w", stderr);
  }

  wargv = CommandLineToArgvW(GetCommandLineW(), &argc);

  char **argv = new char *[argc];
  for(int i = 0; i < argc; i++)
  {
    // allocate pessimistically
    int allocSize = (int)wcslen(wargv[i]) * 4 + 1;

    argv[i] = new char[allocSize];

    WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, &argv[i][0], allocSize, NULL, NULL);
  }

  main(argc, argv);

  delete[] argv;
  LocalFree(wargv);
}

#endif
