#include "windows.h"
#include <cstdlib>
#include "graph.hpp"
#include "graphwindow.hpp"
#include "point.hpp"
#include "waitobject.hpp"

//Constructor: Create the window class and the window, show the window and send it the wm_paint message.
//This constructor also passes a pointer to the object instance to the WM_CREATE message so it can attach it
//to the GWLP_USERDATA property of the window handle.

TGraphWindow::TGraphWindow(HINSTANCE xAppInst,
                           TPaintProc xPaintProc,
                           void *xPaintProcParam,
                           HANDLE xMergePaintEvent, HANDLE xRePaintEvent,
                           int MaxModX, int MaxModY,
                           int ShowCommand,
                           char *Title
                          )
{
 WNDCLASSEX GraphWindowClass;
 RECT ClientRect;

 //Set up class globals
 AppInst = xAppInst; //Application Instance
 PaintProc = xPaintProc; //Procedure which paints the window
 PaintProcParam = xPaintProcParam;
 MergePaintEvent = xMergePaintEvent;
 RePaintEvent = xRePaintEvent;

 //Create the window class
 if (!GetClassInfoEx(AppInst, "TGraphWindow", &GraphWindowClass))
 {
  GraphWindowClass.cbSize = sizeof(WNDCLASSEX); //Always set to this
  GraphWindowClass.style = (CS_OWNDC | CS_HREDRAW | CS_VREDRAW); //Class-wide styles.
  GraphWindowClass.lpfnWndProc = &TGraphWindow::GlobalWndProc; //Window Procedure.
  GraphWindowClass.cbClsExtra = 0; //Bytes to allocate for extra class information.
  GraphWindowClass.cbWndExtra = 0; //Bytes to allocate for extra window information.
  GraphWindowClass.hInstance = AppInst; //Instance of application which created the class.
  GraphWindowClass.hIcon = NULL; //Window Icon.
  GraphWindowClass.hCursor = LoadCursor(NULL, IDC_ARROW); //Window Cursor.
  GraphWindowClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); //Window Background.
  GraphWindowClass.lpszMenuName = NULL; //Name of main menu (if we have one)
  GraphWindowClass.lpszClassName = "TGraphWindow"; //Name of class.
  GraphWindowClass.hIconSm = NULL; //Window Small Icon.

  RegisterClassEx(&GraphWindowClass);
 }

 //Create the window
 Handle = CreateWindowEx(
 WS_EX_APPWINDOW, //Window-wide styles (extended).
 "TGraphWindow", //Class name.
 Title, //Title of window.
 (WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED | WS_MINIMIZEBOX), //Window-wide styles.
 CW_USEDEFAULT, //X Position.
 CW_USEDEFAULT, //Y Position.
 500, //Width
 600, //Height
 (HWND) NULL, //Parent Window
 (HMENU) NULL, //Name of main menu.
 AppInst, //Instance of application which created the class.
 this //Extra user data to pass to WM_CREATE.
 );

 //Set up the painting thread
 DieEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 CreateThread(
 NULL, //Security Attributes
 0, //Stack Size
 &TGraphWindow::GlobalPaintThread, //Start Point
 this, //Start Point Parameter
 0, //Suspend after creation?
 NULL //Thread identifier
 );

 //Device Context for the whole window
 WindowDC = GetDC(Handle);

 //Create the graph
 GetClientRect(Handle, &ClientRect);
 Graph = new TGraph(WindowDC, MaxModX, MaxModY, ClientRect);

 //Show and paint the window
 ShowWindow(Handle, ShowCommand);
 UpdateWindow(Handle);
}

TGraphWindow::~TGraphWindow()
{
 SetEvent(DieEvent);

 ReleaseDC(Handle, WindowDC);
 UnregisterClass("TGraphWindow", AppInst);
}

DWORD WINAPI TGraphWindow::PaintThread()
{
 HANDLE PaintEvents[3] = {DieEvent, MergePaintEvent, RePaintEvent};
 BOOL ExitFlag = FALSE;

 for (;;)
 {
  switch(WaitForMultipleObjects(3, PaintEvents, FALSE, INFINITE))
  {
   case WAIT_OBJECT_2: //"RePaintEvent"
   PaintProgress = 0;
   Graph->Clear();
   PaintProc(Graph, PaintProcParam, &PaintProgress);
   InvalidateRect(Handle, NULL, FALSE);
   break;
   case WAIT_OBJECT_1: //"MergePaintEvent"
   PaintProgress = 0;
   PaintProc(Graph, PaintProcParam, &PaintProgress);
   InvalidateRect(Handle, NULL, FALSE);
   break;
   default: //"DieEvent"
   ExitFlag = TRUE;
   break;
  }
  if (ExitFlag) break;
 }

 return 0;
}

DWORD WINAPI TGraphWindow::GlobalPaintThread(LPVOID self)
{
 return ((TGraphWindow *)self)->PaintThread();
}

LRESULT CALLBACK TGraphWindow::WndProc(HWND hwnd, UINT MsgIdent, WPARAM wParam, LPARAM lParam)
{
 switch(MsgIdent)
 {
  case WM_CREATE:
  return 0;
  case WM_PAINT:
  Graph->BitBltTo(WindowDC);
  ValidateRect(Handle, NULL);
  return 0;
  break;
  default:
  return DefWindowProc(hwnd, MsgIdent, wParam, lParam);
  break;
 }
 return 0;
}

LRESULT CALLBACK TGraphWindow::GlobalWndProc(HWND hwnd, UINT MsgIdent, WPARAM wParam, LPARAM lParam)
{
 TGraphWindow *self;

 switch(MsgIdent)
 {
  case WM_CREATE:
  self = (TGraphWindow *) ((CREATESTRUCT *) lParam)->lpCreateParams;
  SetWindowLongPtr(hwnd, GWLP_USERDATA, ((LONG_PTR) self));
  return self->WndProc(hwnd, MsgIdent, wParam, lParam);
  break;
  default:
  self = (TGraphWindow *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
  return self->WndProc(hwnd, MsgIdent, wParam, lParam);
  break;
 }

 return 0;
}
