/*

MATT WIN GUI API FOR MICROSOFT WINDOWS AND C++...

Author: Matthew W. Coan
Date: Augst 4, 2018

*/

#ifndef _MATT_WIN_H
#define _MATT_WIN_H

#include <windows.h>
#include <CommCtrl.h>
#include <string>
#include <list>
#include <sstream>

LRESULT CALLBACK WinProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);

namespace matt_win {

using namespace std;

class Application {
public:
   Application() {

   }

   Application(const Application & app) {

   }
   
   virtual ~Application() {

   }

   virtual void update() {
   }

   virtual void run() {
	   MSG msg;
	   ZeroMemory(&msg,sizeof(MSG));

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

class Window {
protected:
   HWND hWindow;
   int x,y,width,height;

public:
   Window() { x = y = width = height = 0; hWindow = NULL; }
   void move(int x, int y) {
      this->x = x;
      this->y = y;
      if(hWindow != NULL)
      MoveWindow(hWindow, x, y, width, height, TRUE);
   }
   ~Window() {
      if(hWindow != 0) {
         DestroyWindow(hWindow);
      }
   }
   void update() {
      if(hWindow != NULL)
      UpdateWindow(hWindow);
   }
   void resize(int w, int h) {
      width = w;
      height = h;
      if(hWindow != NULL)
      MoveWindow(hWindow, x, y, width, height, TRUE);
   }
   HWND get_hWnd() { return hWindow; }
   void show() {
      if(hWindow != NULL)
      ShowWindow(hWindow, SW_SHOW);
   }
   void hide() {
      if(hWindow != NULL)
      ShowWindow(hWindow, SW_HIDE);
   }
};

class Frame : public Window {
   string title;
   WNDCLASSEX wClass;
   char szAppName[255];

public:
	Frame(const string & class_name,
      HINSTANCE hInst, 
      int nShowCmd,
      const string & title, 
      int x = 0, 
      int y = 0, 
      int w = 640, 
      int h = 480) {


		this->title = title;
		this->x = x;
		this->y = y;
		this->width = w;
		this->height = h;

      ZeroMemory(&wClass,sizeof(WNDCLASSEX));
      wClass.cbClsExtra=NULL;
      wClass.cbSize=sizeof(WNDCLASSEX);
      wClass.cbWndExtra=NULL;
      wClass.hbrBackground=(HBRUSH)COLOR_WINDOW;
      wClass.hCursor=LoadCursor(NULL,IDC_ARROW);
      wClass.hIcon=NULL;
      wClass.hIconSm=NULL;
      wClass.hInstance=hInst;
      wClass.lpfnWndProc=(WNDPROC)WinProc;
      wClass.lpszClassName="Window Class";
      wClass.lpszMenuName=NULL;
      wClass.style=CS_HREDRAW|CS_VREDRAW;
      wClass.lpszClassName = "Sample Window";

      if(!RegisterClassEx(&wClass))
      {
	      int nResult=GetLastError();
	      MessageBox(NULL,
		   "Window class creation failed\r\n",
		   "Window Class Failed",
		   MB_ICONERROR);
      }

      strcpy(szAppName, class_name.c_str());

      hWindow=CreateWindowEx(
      NULL,
      "Sample Window",
		title.c_str(),
		WS_OVERLAPPEDWINDOW,
		x,
		y,
		width,
		height,
		NULL,
		NULL,
		hInst,
		NULL);

      if(!hWindow) {
         int nResult=GetLastError();
         MessageBox(NULL,
         "Window creation failed\r\n",
         "Window Creation Failed",
         MB_ICONERROR);
      }

      ShowWindow(hWindow,nShowCmd);
      UpdateWindow(hWindow);
	}

	virtual ~Frame() { 
	}

};

class Button : public Window {
   int id;
   string text;

public:
	Button(HWND hWnd, 
   const string & txt, 
   const int id,
   int x, 
   int y, 
   int w, 
   int h) {
      this->x = x;
      this->y = y;
      
      width = w;
      height = h;

      this->id = id;
      text = txt;
      HGDIOBJ hfDefault=GetStockObject(DEFAULT_GUI_FONT);
		hWindow=CreateWindowEx(NULL,
			"BUTTON",
			text.c_str(),
			WS_TABSTOP|WS_VISIBLE|
			WS_CHILD|BS_DEFPUSHBUTTON,
			x,
			y,
			w,
			h,
			hWnd,
			(HMENU)id,
			GetModuleHandle(NULL),
			NULL);
		SendMessage(hWindow,
			WM_SETFONT,
			(WPARAM)hfDefault,
			MAKELPARAM(FALSE,0));
	}

	virtual ~Button() { 
	}

   void set_text(const string & txt) {
      text = txt;
   	SendMessage(hWindow,
		WM_SETTEXT,
		NULL,
		(LPARAM)text.c_str());
   }
};

class Text : public Window {
   int id;
   string text;

public:
	Text(HWND hWnd, 
      const string & txt, 
      int id,
      int x, 
      int y, 
      int w, 
      int h, 
      bool multi_line = true) {

      width = w;
      height = h;
      this->x = x;
      this->y = y;

      text = txt;
      this->id = id;

	   int ml;

	   if(multi_line)
		   ml = WS_CHILD|WS_VISIBLE|
			ES_MULTILINE|ES_AUTOVSCROLL|ES_AUTOHSCROLL;
	   else 
		   ml = WS_CHILD|WS_VISIBLE;

		hWindow=CreateWindowEx(WS_EX_CLIENTEDGE,
			"EDIT",
			"",
			ml,
			x,
			y,
			w,
			h,
			hWnd,
			(HMENU)id,
			GetModuleHandle(NULL),
			NULL);
		HGDIOBJ hfDefault=GetStockObject(DEFAULT_GUI_FONT);
		SendMessage(hWindow,
			WM_SETFONT,
			(WPARAM)hfDefault,
			MAKELPARAM(FALSE,0));
		SendMessage(hWindow,
			WM_SETTEXT,
			NULL,
			(LPARAM)text.c_str());
	}

	virtual ~Text() { 

	}

	string get_text() {
      char buffer[256];
      SendMessage(hWindow,
      WM_GETTEXT,
      sizeof(buffer),
      reinterpret_cast<LPARAM>(buffer));
      return buffer;
	}

   void set_text(const string & txt) {
      text = txt;
      SendMessage(hWindow,
      WM_SETTEXT,
      NULL,
      (LPARAM)text.c_str());
   }
};

class IPAddress : public Window {
   int id;

public:
   void set_ip(int a, int b, int c, int d) {
      LPARAM lpAdr = MAKEIPADDRESS(a, b, c, d);
      SendMessage(hWindow, IPM_SETADDRESS, 0, lpAdr);
   }

	IPAddress(HWND hWnd, 
      int id,
      int x, 
      int y, 
      int w, 
      int h) {

      width = w;
      height = h;
      this->x = x;
      this->y = y;

      this->id = id;

      LPARAM wRangeForField3 = MAKEIPRANGE(0, 256);

		hWindow=CreateWindowEx(0,
			WC_IPADDRESS,
			"0.0.0.0",
			WS_CHILD | WS_VISIBLE,
			x,
			y,
			w,
			h,
			hWnd,
			(HMENU)id,
			GetModuleHandle(NULL),
			NULL);

      SendMessage(hWindow, IPM_SETRANGE, 2, wRangeForField3);

      set_ip(0,0,0,0);
	}

	virtual ~IPAddress() { 

	}

	string get_text() {
      char buffer[256];
      SendMessage(hWindow, IPM_GETADDRESS, 0, (LPARAM)buffer);
      return buffer;
	}
};


class Label : public Window {
   string text;
public:
   Label(HWND hWnd, const string & text, int id, int x, int y, int w, int h) {
      this->text = text;
		hWindow=CreateWindowEx(0,
			"STATIC",
		   NULL,
			WS_CHILD|WS_VISIBLE,
			x,
			y,
			w,
			h,
			hWnd,
			(HMENU)id,
			GetModuleHandle(NULL),
			NULL);
      HGDIOBJ hfDefault=GetStockObject(DEFAULT_GUI_FONT);
		SendMessage(hWindow,
			WM_SETFONT,
			(WPARAM)hfDefault,
			MAKELPARAM(FALSE,0));
		SendMessage(hWindow,
			WM_SETTEXT,
			NULL,
			(LPARAM)text.c_str());
   }
   ~Label() {
   }
};

class Image : public Window {
   HBITMAP hBitmap;
public:
   Image(HWND hWnd, const string & file, int id, int x, int y, int w, int h) {
      hBitmap = (HBITMAP)LoadImage(NULL, file.c_str(), IMAGE_BITMAP,
        0, 0, LR_LOADFROMFILE);
      if(hBitmap != NULL) {
         hWindow = CreateWindowW(L"Static", L"", 
                WS_CHILD | WS_VISIBLE | SS_BITMAP,
                x, y, width, height, hWnd, (HMENU) 1, NULL, NULL);
         SendMessage(hWindow, STM_SETIMAGE,
                 (WPARAM) IMAGE_BITMAP, (LPARAM) hBitmap); 
      }
      else {
         hWindow = NULL;
      }
   }
   ~Image() {
      if(hBitmap != NULL) {
         DeleteObject(hBitmap);
      }
   }
};

class Canvas : public Window {
   HDC wdc;
   HDC hdc;
   COLORREF bg;
   COLORREF fg;
   HFONT hFont;
   int id;
   int line_width;
public:
   Canvas(HWND hWnd,
          const int id,
          const int x, 
          const int y,
          const int width,
          const int height) {
      this->id = id;
      this->x = x;
      this->y = y;
      this->width = width;
      this->height = height;
      line_width = 1;
      hFont = NULL;
      hWindow=CreateWindowEx(
         WS_EX_CLIENTEDGE,
			"CANVAS",
			"",
			WS_VISIBLE | WS_CHILD,
			x,
			y,
			width,
			height,
			hWnd,
			(HMENU)id,
			GetModuleHandle(NULL),
			NULL);
   }

   ~Canvas() {
   }

   virtual void draw() {
      fill_rect(x,y,x+width,y+height,RGB(0,0,0));
   }

   void set_font(const string & font_name, int font_width, int font_height) {
      hFont = CreateFont(
		  font_height,
		  font_width,
		  0,
		  0,
		  0,
		  FALSE,
		  FALSE,
		  FALSE,
		  ANSI_CHARSET,
		  OUT_DEFAULT_PRECIS,
		  CLIP_DEFAULT_PRECIS,
		  DEFAULT_QUALITY,
		  FF_MODERN,
		  font_name.c_str());
      SelectObject(hdc, hFont);
      SetTextColor(hdc, fg);
      SetBkColor(hdc, bg);
   }

   void set_bg(const COLORREF & bg) {
      this->bg = bg;
      SetBkColor(hdc, bg);
   }

   void set_fg(const COLORREF & fg) {
      this->fg = fg;
      SetTextColor(hdc, fg);
   }

   void set_line_width(int lw) {
      line_width = lw;
   }

   int get_text_width(const string & text) {
      int temp = 0;
      int * array = new int[text.size()];
      memset(array, 0, sizeof(int)*text.size());
      GetCharWidth(hdc,0,text.size()-1,array);
      for(int i = 0; i < text.size(); i++) {
         temp += array[i];
      }
      delete [] array;
      return temp;
   }

   void draw_text(const int x, const int y, const string & text) {
      RECT rect;
      rect.left = x;
      rect.top = y;
      rect.right = 0;
      rect.bottom = 0;
      SetBkMode(hdc, TRANSPARENT);
      DrawText(hdc, text.c_str(), -1, &rect, DT_SINGLELINE | DT_NOCLIP);
   }

   void draw_line(const int x1, 
                  const int y1, 
                  const int x2, 
                  const int y2,
				  const COLORREF & color = RGB(255,255,255)) {
	  HPEN pen,oldpen;
	  pen = CreatePen(PS_SOLID, line_width, color);
	  oldpen = (HPEN)SelectObject(hdc,pen);
	  MoveToEx(hdc,x1,y1, NULL);
	  LineTo(hdc,x2,y2);
	  DeleteObject(pen);
	  SelectObject(hdc,oldpen);
   }

   void draw_rect(const int x1, 
                  const int y1, 
                  const int x2, 
                  const int y2,
                  const COLORREF & color = RGB(0,0,255)) {
      draw_line(x1, y1, x2, y1, color);
      draw_line(x1, y1, x1, y2, color);
      draw_line(x1, y2, x2, y2, color);
      draw_line(x2, y2, x2, y1, color);
   }

   void fill_rect(const int x1, 
                  const int y1, 
                  const int x2, 
                  const int y2,
                  const COLORREF & color = RGB(0,0,0)) {
      HBRUSH hBrush = CreateSolidBrush(color);
      HBRUSH oldbrush = (HBRUSH)SelectObject(hdc,hBrush);
      RECT rect;
      rect.left = x1;
      rect.top = y1;
      rect.right = x2;
      rect.bottom = y2;
      FillRect(hdc, &rect, hBrush);
      DeleteObject(hBrush);
      SelectObject(hdc, oldbrush);
   }

   void paint(HWND hWindow) {
      wdc = GetDC(hWindow);
      hdc = CreateCompatibleDC(wdc);
      HBITMAP bmp = CreateBitmap(width,height,1,32,NULL);
      HANDLE h = (HANDLE)SelectObject(hdc,bmp);
      draw();
      RECT rect;
      SetRect(&rect,0,0,width,height);
      DrawEdge(hdc,&rect,EDGE_SUNKEN,BF_RECT);
      BitBlt(wdc,x,y,x+width,y+height,hdc,0,0,SRCCOPY);
      SelectObject(hdc,h);
      DeleteDC(hdc);
      DeleteObject(bmp);
      DeleteDC(wdc);
   }
};

class ListBox : public Window {
   list< string > my_list;
public:
   ListBox(HWND hWnd, int id, int x, int y, int w, int h) {
		hWindow=CreateWindowEx(WS_EX_CLIENTEDGE,
			"LISTBOX",
		   NULL,
			WS_CHILD|WS_VISIBLE|LBS_STANDARD|LBS_NOTIFY,
			x,
			y,
			w,
			h,
			hWnd,
			(HMENU)id,
			GetModuleHandle(NULL),
			NULL);
   }
   void add(const string & item) {
      my_list.push_back(item);
      SendMessage(hWindow, LB_ADDSTRING, 0, (LPARAM)my_list.back().c_str());
   }
   string get_selection() {
      char buffer[256];
      int index = SendMessage(hWindow, LB_GETCARETINDEX, 0, 0);
      SendMessage(hWindow, LB_GETTEXT, (LPARAM)index, (WPARAM)buffer);
      return buffer;
   }
   int get_selection_index() {
      int index = SendMessage(hWindow, LB_GETCARETINDEX, 0, 0);
      return index;
   }
};

class ComboBox : public Window {
   list< string > value_list;
public:
   ComboBox(HWND hWnd, int id, int x, int y, int width, int height) {
      hWindow = CreateWindow("COMBOBOX", TEXT(""), 
      CBS_DROPDOWN | CBS_HASSTRINGS | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE,
      x, y, width, height, hWnd, (HMENU)id, GetModuleHandle(NULL),
      NULL);
   }

   void add(const string & str) {
      value_list.push_back(str);
      SendMessage(hWindow,(UINT) CB_ADDSTRING,(WPARAM) 0,(LPARAM) value_list.back().c_str());
   }

   int get_selection_index() {
      int index = SendMessage((HWND) hWindow, (UINT) CB_GETCURSEL, 
      (WPARAM) 0, (LPARAM) 0);
      return index;
   }
  
   string get_selection() {
      int index = SendMessage((HWND) hWindow, (UINT) CB_GETCURSEL, 
      (WPARAM) 0, (LPARAM) 0);
      TCHAR  buffer[256];
      (TCHAR) SendMessage((HWND) hWindow, (UINT) CB_GETLBTEXT, 
      (WPARAM) index, (LPARAM) buffer);
      return buffer;
   }

   void set_selection(int index) {
      SendMessage(hWindow, CB_SETCURSEL, (WPARAM)index, (LPARAM)0);
   }
};

class TabControl : public Window {

public:
   TabControl(HWND hWnd, int id, int x, int y, int w, int h) {
    RECT rcClient; 
    INITCOMMONCONTROLSEX icex;
    HWND hwndTab; 
    TCITEM tie; 
    int i; 
    TCHAR achTemp[256];  // Temporary buffer for strings.
 
    // Initialize common controls.
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC = ICC_TAB_CLASSES;
    //InitCommonControlsEx(&icex);
      hWindow = CreateWindow(
        WC_TABCONTROL, 
        "", 
        WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, 
        x, 
        y, 
        w, 
        h, 
        hWnd, 
        (HMENU)id, 
        GetModuleHandle(NULL), 
        NULL); 
   }

   void add(const int index, const string & str) {
      TCITEM tie; 
      tie.mask = TCIF_TEXT | TCIF_IMAGE; 
      tie.iImage = -1; 
      tie.pszText = (TCHAR*)str.c_str(); 
      TabCtrl_InsertItem(hWindow, index, &tie); 
   }

   int get_current_index() {
      return TabCtrl_GetCurSel(hWindow);
   }
   
   void set_current_index(int index) {
      TabCtrl_SetCurSel(hWindow, index);
   }
};

}


#endif /* _MATT_WIN_H */
