#include "config.h"

#if defined(BUILD_WIN32) && !defined(MEM_CHECK)

#include "latex.h"
#include "platform/gdi_win/graphic_win32.h"
#include "samples.h"

#include <time.h>
#include <cstdlib>
#include <iostream>

#include <comdef.h>
#include <gdiplus.h>
#include <objidl.h>
#include <tchar.h>
#include <windows.h>

#define ID_SETTER 256
#define ID_CANVAS 512
#define ID_EDITBOX 1024
#define ID_BUTTON_SIZE 2048
#define ID_BUTTON_RANDOM 4096

#define BUTTON_WIDTH 140
#define BUTTON_HEIGHT 35
#define EDITOR_WIDTH 480

using namespace std;
using namespace tex;
using namespace Gdiplus;

TeXRender* _render = nullptr;
int _size = 26;
color _color = 0xff424242;
Samples* _samples;

HWND hEditor, hBtnSize, hBtnRandom, hCanvas, hEditSize;
WNDPROC editorProc, setterProc;

void RegisterCanvas();

void CreateCtrl(HINSTANCE, HWND);

void ResizeCtrl(HWND);

void HandleOK();

void HandleRandom();

void HandleSize();

void RenderFormula(HWND, HDC);

void init();

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK CanvasProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK EditorProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK SetterProc(HWND, UINT, WPARAM, LPARAM);

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow) {
  HWND hWnd;
  MSG msg;
  WNDCLASS wndClass;
  GdiplusStartupInput gdiplusStartupInput;
  ULONG_PTR gdiplusToken;

  // Initialize GDI+.
  GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
  init();

  RegisterCanvas();

  wndClass.style = CS_HREDRAW | CS_VREDRAW;
  wndClass.lpfnWndProc = WndProc;
  wndClass.cbClsExtra = 0;
  wndClass.cbWndExtra = 0;
  wndClass.hInstance = hInstance;
  wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wndClass.lpszMenuName = NULL;
  wndClass.lpszClassName = TEXT("LaTeX");

  RegisterClass(&wndClass);

  hWnd = CreateWindow(
      TEXT("LaTeX"),        // window class name
      TEXT("LaTeX"),        // window caption
      WS_OVERLAPPEDWINDOW,  // window style
      0,                    // initial x position
      0,                    // initial y position
      780,                  // initial x size
      480,                  // initial y size
      NULL,                 // parent window handle
      NULL,                 // window menu handle
      hInstance,            // program instance handle
      NULL);                // creation parameters

  CreateCtrl(hInstance, hWnd);
  ShowWindow(hWnd, SW_SHOWMAXIMIZED);
  UpdateWindow(hWnd);

  while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  delete _render;
  delete _samples;
  LaTeX::release();
  GdiplusShutdown(gdiplusToken);
  return msg.wParam;
}  // WinMain

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  switch (message) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    case WM_COMMAND:
      if (wParam == ID_BUTTON_SIZE) {
        HandleSize();
      } else if (wParam == ID_BUTTON_RANDOM) {
        HandleRandom();
      }
      return 0;
    case WM_SIZE:
      ResizeCtrl(hWnd);
      return 0;
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
}  // WndProc

void init() {
  LaTeX::init();
  _samples = new Samples();
  _render = LaTeX::parse(L"\\text{What a beautiful day}", 720, _size, _size / 3.f, _color);
}

void HandleRandom() {
  srand(time(NULL));
  if (_render != nullptr) {
    delete _render;
  }
  RECT r;
  GetClientRect(hCanvas, &r);
  _render = LaTeX::parse(_samples->next(), r.right - r.left, _size, _size / 3.f, _color);
  InvalidateRect(hCanvas, NULL, TRUE);
  UpdateWindow(hCanvas);
}

void HandleOK() {
  int len = GetWindowTextLengthW(hEditor);
  wchar_t* txt = new wchar_t[len + 10];
  GetWindowTextW(hEditor, txt, len + 10);
  if (_render != nullptr) {
    delete _render;
  }
  RECT r;
  GetClientRect(hCanvas, &r);
  _render = LaTeX::parse(std::wstring(txt), r.right - r.left, _size, _size / 3.f, _color);
  InvalidateRect(hCanvas, NULL, TRUE);
  UpdateWindow(hCanvas);
  delete[] txt;
}

void HandleSize() {
  if (_render == nullptr)
    return;
  int len = GetWindowTextLength(hEditSize);
  if (len == 0)
    return;
  char* txt = new char[len + 10];
  GetWindowText(hEditSize, txt, len + 10);
  string x = txt;
  valueof(x, _size);
  _render->setTextSize(_size);
  InvalidateRect(hCanvas, NULL, TRUE);
  UpdateWindow(hCanvas);
  delete[] txt;
}

void RenderFormula(HWND hwnd, HDC hdc) {
  // draw rectangle
  RECT r;
  GetClientRect(hwnd, &r);
  Graphics g(hdc);
  Graphics2D_win32 g2(&g);
  g2.drawRect(r.left, r.top, r.right - r.left - 1, r.bottom - r.top - 1);
  // draw formula
  if (_render != nullptr) {
    _render->draw(g2, 10, 10);
  }
}

void CreateCtrl(HINSTANCE hInst, HWND hwnd) {
  // create first, relayout through message WM_SIZE
  int l = 0, t = 0, w = 10, h = 10;
  // edit-box
  hEditor = CreateWindowEx(
      WS_EX_TOPMOST,
      "edit",
      NULL,
      WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_AUTOVSCROLL,
      l,
      t,
      w,
      h,
      hwnd,
      (HMENU)ID_EDITBOX,
      hInst,
      NULL);
  HFONT hf = CreateFont(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Consolas");
  SendMessage(hEditor, WM_SETFONT, (WPARAM)hf, 0);
  SetFocus(hEditor);
  editorProc = (WNDPROC)SetWindowLongPtr(hEditor, GWLP_WNDPROC, (LONG_PTR)EditorProc);
  // size setter
  hEditSize = CreateWindowEx(
      WS_EX_TOPMOST,
      "edit",
      NULL,
      WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER | ES_NUMBER,
      l,
      t,
      w,
      h,
      hwnd,
      (HMENU)ID_SETTER,
      hInst,
      NULL);
  SendMessage(hEditSize, WM_SETFONT, (WPARAM)hf, 0);
  setterProc = (WNDPROC)SetWindowLongPtr(hEditSize, GWLP_WNDPROC, (LONG_PTR)SetterProc);
  // button size
  hBtnSize = CreateWindowEx(
      WS_EX_TOPMOST,
      "Button",
      NULL,
      WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
      l,
      t,
      w,
      h,
      hwnd,
      (HMENU)ID_BUTTON_SIZE,
      hInst,
      NULL);
  SendMessage(hBtnSize, WM_SETFONT, (WPARAM)hf, 0);
  SendMessage(hBtnSize, WM_SETTEXT, (WPARAM)NULL, (LPARAM)("Set Text Size"));
  // button random
  hBtnRandom = CreateWindowEx(
      WS_EX_TOPMOST,
      "Button",
      NULL,
      WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
      l,
      t,
      w,
      h,
      hwnd,
      (HMENU)ID_BUTTON_RANDOM,
      hInst,
      NULL);
  SendMessage(hBtnRandom, WM_SETFONT, (WPARAM)hf, 0);
  SendMessage(hBtnRandom, WM_SETTEXT, (WPARAM)NULL, (LPARAM)("Random Example"));
  // canvas
  hCanvas = CreateWindowEx(
      WS_EX_TOPMOST,
      "canvas",
      NULL,
      WS_CHILD | WS_VISIBLE,
      l,
      t,
      w,
      h,
      hwnd,
      (HMENU)ID_CANVAS,
      hInst,
      NULL);
}

void ResizeCtrl(HWND hwnd) {
  RECT r;
  GetClientRect(hwnd, &r);
  const int margin = 10;
  // editor
  int w = EDITOR_WIDTH, h = BUTTON_HEIGHT;
  int l = r.right - w - margin, t = r.top + margin;
  h = r.bottom - t - h - 2 * margin;
  MoveWindow(hEditor, l, t, w, h, TRUE);
  // setter
  w = EDITOR_WIDTH - BUTTON_WIDTH * 2 - margin * 2, h = BUTTON_HEIGHT, t = r.bottom - h - margin;
  MoveWindow(hEditSize, l, t, w, h, TRUE);
  // button size
  w = BUTTON_WIDTH;
  l = r.right - w * 2 - margin * 2;
  MoveWindow(hBtnSize, l, t, w, h, TRUE);
  // button random
  w = BUTTON_WIDTH;
  l = r.right - w - margin;
  MoveWindow(hBtnRandom, l, t, w, h, TRUE);
  // canvas
  l = r.left + margin, t = r.top + margin;
  w = r.right - EDITOR_WIDTH - 2 * margin - l, h = r.bottom - t - margin;
  MoveWindow(hCanvas, l, t, w, h, TRUE);
}

void RegisterCanvas() {
  WNDCLASS wnd = {0};
  wnd.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
  wnd.lpfnWndProc = CanvasProc;
  wnd.hCursor = LoadCursor(NULL, IDC_ARROW);
  wnd.lpszClassName = "canvas";
  RegisterClass(&wnd);
}

LRESULT CALLBACK EditorProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
  if (msg == WM_KEYDOWN && wp == VK_RETURN && GetKeyState(VK_CONTROL) < 0) {
    HandleOK();
    return 0;
  }
  if (msg == WM_CHAR && wp == '\n' && GetKeyState(VK_CONTROL) < 0) {
    return 0;
  }
  return CallWindowProc(editorProc, hwnd, msg, wp, lp);
}

LRESULT CALLBACK SetterProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
  if (msg == WM_KEYDOWN && wp == VK_RETURN) {
    HandleSize();
    return 0;
  }
  return CallWindowProc(setterProc, hwnd, msg, wp, lp);
}

void ScrollCanvas(int d) {
  ScrollWindow(hCanvas, 0, d, NULL, NULL);
  UpdateWindow(hCanvas);
}

LRESULT CALLBACK CanvasProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  HDC hdc;
  PAINTSTRUCT ps;

  switch (message) {
    case WM_PAINT: {
      hdc = BeginPaint(hWnd, &ps);
      RenderFormula(hWnd, hdc);
      EndPaint(hWnd, &ps);
      break;
    }
    case WM_ERASEBKGND: {
      hdc = (HDC)wParam;
      RECT r;
      GetClientRect(hWnd, &r);
      FillRect(hdc, &r, (HBRUSH)GetStockObject(WHITE_BRUSH));
      break;
    }
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

#endif
