/* The original version of this program can be found at http://damb.dk */
#define _WIN32_IE  0x0500
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <vector>
#include <fstream>
#include <iostream>
#include <istream>

HINSTANCE InstanceHandle;
HWND      MainWindow;
HWND      StatusBarWindow;
HWND      ToolBarWindow;

COLORREF CurrentColor = RGB(255, 0, 0);

enum CurrentObjectType
{
   LineObj,
   RectangleObj
};

CurrentObjectType CurrentObject = LineObj;
char CurrentFileName[256];

enum MenuCommand
{
   FileOpenCmd = 1000,
   FileSaveCmd,
   FileSaveBmpCmd,
   FileCloseCmd,
   DrawLineCmd,
   DrawRectangleCmd,
   DrawRedCmd,
   DrawBlueCmd,
   DrawGreenCmd,
   AboutCmd
};

enum WindowId
{
   StatusBarId = 128,
   ToolBarId
};

class ObjectBaseClass
{
public:
   virtual void SetEnd(HDC &aDc, POINT aEnd) = 0;
   virtual void Draw(HDC &dc, bool aMoving = false)  = 0;
   virtual void Write(std::ostream &os) = 0;
   virtual bool Read(std::istream &is) = 0;
};

class LineClass : public ObjectBaseClass
{
public:
   LineClass(){}
   LineClass(POINT aStart, COLORREF aColor) :
      Start(aStart),
      End(aStart),
      Color(aColor),
      First(true)
   {}

   void SetEnd(HDC &aDc, POINT aEnd)
   {
      Draw(aDc, true);
      End = aEnd;
      Draw(aDc, true);
   }
   void Draw(HDC &dc, bool aMoving = false);
   void Write(std::ostream &os);
   bool Read(std::istream &is);
private:
   POINT Start;
   POINT End;
   COLORREF Color;
   bool First;
};

class RectangleClass : public ObjectBaseClass
{
public:
   RectangleClass(){}
   RectangleClass(POINT aStart, COLORREF aColor) :
      Start(aStart),
      End(aStart),
      Color(aColor),
      First(true)
   {}

   void SetEnd(HDC &aDc, POINT aEnd)
   {
      Draw(aDc, true);
      End = aEnd;
      Draw(aDc, true);
   }
   void Draw(HDC &dc, bool aMoving = false);
   void Write(std::ostream &os);
   bool Read(std::istream &is);
private:
   POINT Start;
   POINT End;
   COLORREF Color;
   bool First;
};

std::ostream &operator << (std::ostream &os, const POINT aP)
{
   os << aP.x << " " << aP.y;
   return os;
}

std::istream &operator >> (std::istream &is, POINT &aP)
{
   is >> aP.x;
   is >> aP.y;
   return is;
}

void LineClass::Draw(HDC &aDc, bool aMoving)
{
   if(First)
   {
      First = false;
      return;
   }

   if(aMoving)
   {
      SetROP2(aDc, R2_NOTXORPEN);
   }
   else
   {
      SetROP2(aDc, R2_COPYPEN);
   }
   HPEN Pen = CreatePen(PS_SOLID, 1, Color);
   HPEN OldPen = (HPEN )SelectObject(aDc, Pen);
   MoveToEx(aDc, Start.x, Start.y, 0);
   LineTo(aDc, End.x, End.y);
   SelectObject(aDc, OldPen);
   DeleteObject(Pen);
}

void LineClass::Write(std::ostream &os)
{
   os << LineObj << std::endl;
   os << Start << std::endl;
   os << End << std::endl;
   os << Color << std::endl;
}

bool LineClass::Read(std::istream &is)
{
   is >> Start;
   is >> End;
   is >> Color;
   return is;
}

void RectangleClass::Write(std::ostream &os)
{
   os << RectangleObj << std::endl;
   os << Start << std::endl;
   os << End << std::endl;
   os << Color << std::endl;
}

bool RectangleClass::Read(std::istream &is)
{
   is >> Start;
   is >> End;
   is >> Color;
   return is;
}

void RectangleClass::Draw(HDC &aDc, bool aMoving)
{
   if(First)
   {
      First = false;
      return;
   }

   if(aMoving)
   {
      SetROP2(aDc, R2_NOTXORPEN);
   }
   else
   {
      SetROP2(aDc, R2_COPYPEN);
   }
   HPEN Pen = CreatePen(PS_SOLID, 1, Color);
   HPEN OldPen = (HPEN )SelectObject(aDc, Pen);
   MoveToEx(aDc, Start.x, Start.y, 0);
   LineTo(aDc, End.x, Start.y);
   LineTo(aDc, End.x, End.y);
   LineTo(aDc, Start.x, End.y);
   LineTo(aDc, Start.x, Start.y);
   SelectObject(aDc, OldPen);
   DeleteObject(Pen);
}

std::vector<ObjectBaseClass *>Objects;
bool IsDrawing;

void OnLButtonDown(HWND aHwnd, int aX, int aY)
{
   POINT P;
   P.x = aX;
   P.y = aY;
   ObjectBaseClass *Object;
   if(CurrentObject == LineObj)
      Object = new LineClass(P, CurrentColor);
   else
      Object = new RectangleClass(P, CurrentColor);
   Objects.push_back(Object);
   IsDrawing = true;
}

void OnMouseMove(HWND aHwnd, int aX, int aY)
{
   if(IsDrawing)
   {
      POINT P;
      P.x = aX;
      P.y = aY;
      HDC dc = GetDC(aHwnd);
      ObjectBaseClass *Last = Objects.back();
      Last->SetEnd(dc, P);
   }
}

void OnLButtonUp(HWND aHwnd, int aX, int aY)
{
   if(IsDrawing)
   {
      POINT P;
      P.x = aX;
      P.y = aY;
      ObjectBaseClass *Last = Objects.back();
      HDC dc = GetDC(aHwnd);
      Last->SetEnd(dc, P);
      Last->Draw(dc);
      ReleaseDC(aHwnd, dc);
      IsDrawing = false;
   }
}

void OnPaint(HWND aHwnd)
{
   PAINTSTRUCT PaintStruct;
   BeginPaint(aHwnd, &PaintStruct);
   size_t i;
   for(i = 0; i < Objects.size(); i++)
      Objects[i]->Draw(PaintStruct.hdc);
   EndPaint(aHwnd, &PaintStruct);
}

void SetStatusBarWidths(HWND ParentWindow)
{
   int BoxRight[3];
   RECT ParentRect;
   GetClientRect(ParentWindow, &ParentRect);
   int Width = ParentRect.right - ParentRect.left;
   BoxRight[0] = 100;
   BoxRight[1] = Width - 100;
   BoxRight[2] = -1;
   SendMessage(StatusBarWindow, SB_SETPARTS, 3, (LPARAM )BoxRight);
}

void SetStatusBarText(const char *Text, WORD PartNumber)
{
   SendMessage(StatusBarWindow, SB_SETTEXT, PartNumber, (LPARAM )Text);
}

bool CreateToolBar(HWND ParentWindow)
{
   const int NumButton = 7;

   HBITMAP ToolBarBitmap = (HBITMAP )LoadImage(0,
                                               "toolbar.bmp",
                                               IMAGE_BITMAP,
                                               80,
                                               16,
                                               LR_LOADFROMFILE);
   if(!ToolBarBitmap)
   {
      MessageBox(ParentWindow, "Failed to load toolbar bitmap!", "Whatever", MB_OK | MB_ICONEXCLAMATION);
      return false;
   }

   ToolBarWindow = CreateWindowEx(0,
                                  TOOLBARCLASSNAME,
                                  0,
                                  WS_CHILD|WS_BORDER,
                                  0, 0, 0, 0,
                                  ParentWindow,
                                  (HMENU )ToolBarId,
                                  InstanceHandle,
                                  0);
   SendMessage(ToolBarWindow,
               TB_BUTTONSTRUCTSIZE,
               (WPARAM )sizeof(TBBUTTON),
               0);

   TBADDBITMAP TBaddBitmap;
   TBaddBitmap.hInst = 0;
   TBaddBitmap.nID   = (UINT )ToolBarBitmap;
   SendMessage(ToolBarWindow,
               TB_ADDBITMAP,
               (WPARAM )NumButton,
               (LPARAM )&TBaddBitmap);

   TBBUTTON ButtonInfo[NumButton];
   memset(ButtonInfo, 0, sizeof(ButtonInfo));
   int idx;
   idx = SendMessage(ToolBarWindow,
                     TB_ADDSTRING,
                     0,
                     (LPARAM )"");
   ButtonInfo[0].iString = idx;
   ButtonInfo[0].iBitmap = 0;
   ButtonInfo[0].idCommand = 0;
   ButtonInfo[0].fsState = TBSTATE_ENABLED;
   ButtonInfo[0].fsStyle = BTNS_SEP;

   idx = SendMessage(ToolBarWindow,
                     TB_ADDSTRING,
                     0,
                     (LPARAM )"Rectangle");
   ButtonInfo[1].iString = idx;
   ButtonInfo[1].iBitmap = 0;
   ButtonInfo[1].idCommand = DrawRectangleCmd;
   ButtonInfo[1].fsState = TBSTATE_ENABLED;
   ButtonInfo[1].fsStyle = TBSTYLE_CHECKGROUP;

   idx = SendMessage(ToolBarWindow,
                     TB_ADDSTRING,
                     0,
                     (LPARAM )"Line");
   ButtonInfo[2].iString = idx;
   ButtonInfo[2].iBitmap = 1;
   ButtonInfo[2].idCommand = DrawLineCmd;
   ButtonInfo[2].fsState = TBSTATE_ENABLED | TBSTATE_CHECKED;
   ButtonInfo[2].fsStyle = TBSTYLE_CHECKGROUP;

   idx = SendMessage(ToolBarWindow,
                     TB_ADDSTRING,
                     0,
                     (LPARAM )"");
   ButtonInfo[3].iString = idx;
   ButtonInfo[3].iBitmap = 0;
   ButtonInfo[3].idCommand = 0;
   ButtonInfo[3].fsState = TBSTATE_ENABLED;
   ButtonInfo[3].fsStyle = BTNS_SEP;

   idx = SendMessage(ToolBarWindow,
                     TB_ADDSTRING,
                     0,
                     (LPARAM )"Red");
   ButtonInfo[4].iString = idx;
   ButtonInfo[4].iBitmap = 2;
   ButtonInfo[4].idCommand = DrawRedCmd;
   ButtonInfo[4].fsState = TBSTATE_ENABLED | TBSTATE_CHECKED;
   ButtonInfo[4].fsStyle = TBSTYLE_CHECKGROUP;

   idx = SendMessage(ToolBarWindow,
                     TB_ADDSTRING,
                     0,
                     (LPARAM )"Green");
   ButtonInfo[5].iString = idx;
   ButtonInfo[5].iBitmap = 3;
   ButtonInfo[5].idCommand = DrawGreenCmd;
   ButtonInfo[5].fsState = TBSTATE_ENABLED;
   ButtonInfo[5].fsStyle = TBSTYLE_CHECKGROUP;

   idx = SendMessage(ToolBarWindow,
                     TB_ADDSTRING,
                     0,
                     (LPARAM )"Blue");
   ButtonInfo[6].iString = idx;
   ButtonInfo[6].iBitmap = 4;
   ButtonInfo[6].idCommand = DrawBlueCmd;
   ButtonInfo[6].fsState = TBSTATE_ENABLED;
   ButtonInfo[6].fsStyle = TBSTYLE_CHECKGROUP;

   SendMessage(ToolBarWindow,
               TB_ADDBUTTONS,
               NumButton,
               (LPARAM)&ButtonInfo);
   SendMessage(ToolBarWindow, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS);
   ShowWindow(ToolBarWindow, SW_SHOW);
   return true;
}

void OnSave()
{
   static char Filter[] = "dat (*.dat)\0*.dat\0All (*.*)\0*.*\0";
   OPENFILENAME OpenFilename;
   memset(&OpenFilename, 0, sizeof(OpenFilename));
   OpenFilename.lStructSize = sizeof(OpenFilename);
   OpenFilename.hwndOwner = MainWindow;
   OpenFilename.hInstance = InstanceHandle;
   OpenFilename.lpstrFilter = Filter;
   OpenFilename.lpstrFile = CurrentFileName;
   OpenFilename.nMaxFile = sizeof(CurrentFileName);
   OpenFilename.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
   OpenFilename.lpstrDefExt = "dat";

   if(GetSaveFileName(&OpenFilename))
   {
      std::ofstream File(CurrentFileName);
      if(File)
      {
         size_t i;
         for(i = 0; i < Objects.size(); i++)
         {
            Objects[i]->Write(File);
         }
         SetStatusBarText(CurrentFileName, 1);
      }
      else
      {
         std::string Msg = "Failed to open:\r\n";
         Msg += CurrentFileName;
         MessageBox(MainWindow, Msg.c_str(), "Whatever", MB_OK | MB_ICONWARNING);
      }
   }
}

void OnOpen()
{
   size_t i;
   for(i = 0; i < Objects.size(); i++)
      delete Objects[i];
   Objects.clear();

   static char Filter[] = "dat (*.dat)\0*.dat\0All (*.*)\0*.*\0";
   OPENFILENAME OpenFilename;
   memset(&OpenFilename, 0, sizeof(OpenFilename));
   OpenFilename.lStructSize = sizeof(OpenFilename);
   OpenFilename.hwndOwner = MainWindow;
   OpenFilename.hInstance = InstanceHandle;
   OpenFilename.lpstrFilter = Filter;
   OpenFilename.lpstrFile = CurrentFileName;
   OpenFilename.nMaxFile = sizeof(CurrentFileName);
   OpenFilename.Flags = OFN_FILEMUSTEXIST;
   OpenFilename.lpstrDefExt = "dat";

   if(GetOpenFileName(&OpenFilename))
   {
      std::ifstream File(CurrentFileName);
      if(File)
      {
         int ObjType;
         while(File >> ObjType)
         {
            ObjectBaseClass *Obj;
            if(ObjType == LineObj)
               Obj = new LineClass();
            else if(ObjType == RectangleObj)
               Obj = new RectangleClass;
            else
               MessageBox(MainWindow, "Unknown Object Type", "Whatever", MB_OK);
            Obj->Read(File);
            Objects.push_back(Obj);
         }
         SetStatusBarText(CurrentFileName, 1);
      }
      else
      {
         std::string Msg = "Failed to open:\r\n";
         Msg += CurrentFileName;
         MessageBox(MainWindow, Msg.c_str(), "Whatever", MB_OK | MB_ICONWARNING);
      }
   }
   InvalidateRect(MainWindow, 0, TRUE);
}

BITMAPINFOHEADER WriteHeader(std::ostream &os, int aWidth, int aHeight, WORD aBitPixel)
{
   BITMAPFILEHEADER BitmapFileHeader;
   BITMAPINFOHEADER BitmapInfoHeader;
   unsigned int WordsPerLine = (aWidth*aBitPixel + 31)/32;
   BitmapFileHeader.bfType = (('M' << 8) | 'B');
   BitmapFileHeader.bfSize = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + 4*WordsPerLine*aHeight;
   BitmapFileHeader.bfReserved1 = 0;
   BitmapFileHeader.bfReserved2 = 0;
   BitmapFileHeader.bfOffBits = 0x36 + 8;

   BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
   BitmapInfoHeader.biWidth = aWidth;
   BitmapInfoHeader.biHeight = aHeight;
   BitmapInfoHeader.biPlanes = 1;
   BitmapInfoHeader.biBitCount = aBitPixel;
   BitmapInfoHeader.biCompression = BI_RGB;
   BitmapInfoHeader.biSizeImage = 0;
   BitmapInfoHeader.biXPelsPerMeter = 0;
   BitmapInfoHeader.biYPelsPerMeter = 0;
   BitmapInfoHeader.biClrUsed = 0;
   BitmapInfoHeader.biClrImportant = 0;
   os.write((char *)&BitmapFileHeader, sizeof(BitmapFileHeader));
   os.write((char *)&BitmapInfoHeader, sizeof(BitmapInfoHeader));
   return BitmapInfoHeader;
};

void OnSaveBmp()
{
   static char Filter[] = "bmp (*.bmp)\0*.bmp\0All (*.*)\0*.*\0";
   OPENFILENAME OpenFilename;
   memset(&OpenFilename, 0, sizeof(OpenFilename));
   OpenFilename.lStructSize = sizeof(OpenFilename);
   OpenFilename.hwndOwner = MainWindow;
   OpenFilename.hInstance = InstanceHandle;
   OpenFilename.lpstrFilter = Filter;
   OpenFilename.lpstrFile = CurrentFileName;
   OpenFilename.nMaxFile = sizeof(CurrentFileName);
   OpenFilename.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
   OpenFilename.lpstrDefExt = "bmp";

   if(GetSaveFileName(&OpenFilename))
   {
      std::ofstream File(CurrentFileName);
      if(File)
      {
         HDC dc = GetDC(MainWindow);
         HDC MemDc = CreateCompatibleDC(dc);
         RECT Rect;
         GetClientRect(MainWindow, &Rect);

         LONG Width = Rect.right - Rect.left;
         LONG Height = Rect.bottom - Rect.top;
         HBITMAP Bitmap = CreateCompatibleBitmap(dc, Width, Height);
         HGDIOBJ OldBitmap = SelectObject(MemDc,  Bitmap);
         HBRUSH Brush = CreateSolidBrush(RGB(255, 255, 255));
         FillRect(MemDc, &Rect, Brush);

         RECT Temp;
         GetWindowRect(ToolBarWindow, &Temp);
         LONG ToolBarHeight = Temp.bottom - Temp.top;
         GetWindowRect(StatusBarWindow, &Temp);
         LONG StatusBarHeight = Temp.bottom - Temp.top;

         SetViewportOrgEx(MemDc, 0, -ToolBarHeight, 0);
         size_t i;
         for(i = 0; i < Objects.size(); i++)
            Objects[i]->Draw(MemDc);

         Height -= StatusBarHeight + ToolBarHeight;
         BITMAP Bm;
         GetObject(Bitmap, sizeof(Bm), &Bm);
         BITMAPINFOHEADER Header = WriteHeader(File, Width, Height, Bm.bmBitsPixel);
         char *Buffer = new char [Width*Height*Bm.bmBitsPixel/8];
         GetDIBits(MemDc, Bitmap, 0, Height, Buffer, (BITMAPINFO *)&Header, DIB_RGB_COLORS);
         File.write(Buffer, Height*Width*Bm.bmBitsPixel/8);

         delete [] Buffer;
         SelectObject(MemDc,  OldBitmap);
         DeleteDC(MemDc);
         DeleteDC(dc);
         DeleteObject(Brush);
         DeleteObject(Bitmap);
      }
   }
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg)
   {
      case WM_DESTROY:
         PostQuitMessage(0);
         break;
      case WM_SIZE:
         SendMessage(StatusBarWindow, msg, wParam, lParam);
         SendMessage(ToolBarWindow, msg, wParam, lParam);
         SetStatusBarWidths(hwnd);
         break;
      case WM_COMMAND:
         switch(LOWORD(wParam))
         {
         case FileOpenCmd:
            OnOpen();
            break;
         case FileSaveCmd:
            OnSave();
            break;
         case FileSaveBmpCmd:
            OnSaveBmp();
            break;
         case FileCloseCmd:
            MessageBox(hwnd, "Close", "Test", MB_OK);
            break;
         case AboutCmd:
            MessageBox(hwnd, "About", "Test", MB_OK);
            break;
         case DrawRedCmd:
            CurrentColor = RGB(255, 0, 0);
            break;
         case DrawBlueCmd:
            CurrentColor = RGB(0, 0, 255);
            break;
         case DrawGreenCmd:
            CurrentColor = RGB(0, 255, 0);
            break;
         case DrawRectangleCmd:
            CurrentObject = RectangleObj;
            break;
         case DrawLineCmd:
            CurrentObject = LineObj;
            break;
         }
         break;
      case WM_LBUTTONDOWN:
         OnLButtonDown(hwnd, LOWORD(lParam), HIWORD(lParam));
         break;
      case WM_MOUSEMOVE:
         OnMouseMove(hwnd, LOWORD(lParam), HIWORD(lParam));
         break;
      case WM_LBUTTONUP:
         OnLButtonUp(hwnd, LOWORD(lParam), HIWORD(lParam));
         break;
      case WM_PAINT:
         OnPaint(hwnd);
         break;
      default:
         return DefWindowProc(hwnd,msg,wParam,lParam);
   }
   return 0;
}

HWND CreateMainWindow()
{
   WNDCLASS wc;
   memset(&wc, 0, sizeof(WNDCLASS));
   wc.style = CS_HREDRAW | CS_VREDRAW  | CS_DBLCLKS ;
   wc.lpfnWndProc = (WNDPROC )MainWndProc;
   wc.hInstance = InstanceHandle;
   wc.hbrBackground = (HBRUSH )(COLOR_WINDOW + 1);
   wc.lpszClassName = "SimpleWinWndClass";
   wc.lpszMenuName = 0;
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
   if(!RegisterClass(&wc))
      return 0;

   HMENU FileMenu = CreateMenu();
   AppendMenu(FileMenu, MF_STRING, FileOpenCmd, "Open");
   AppendMenu(FileMenu, MF_STRING, FileSaveCmd, "Save");
   AppendMenu(FileMenu, MF_STRING, FileSaveBmpCmd, "Save BMP");
   AppendMenu(FileMenu, MF_SEPARATOR, 0 , 0);
   AppendMenu(FileMenu, MF_STRING, FileCloseCmd, "Close");

   HMENU HelpMenu = CreateMenu();
   AppendMenu(HelpMenu, MF_STRING, AboutCmd, "About");

   HMENU MainMenu = CreateMenu();
   AppendMenu(MainMenu, MF_POPUP, (UINT )FileMenu, "File");
   AppendMenu(MainMenu, MF_POPUP, (UINT )HelpMenu, "Help");

   return CreateWindow("SimpleWinWndClass",
                       "Simple-Window",
                       WS_MINIMIZEBOX | WS_VISIBLE |
                       WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_MAXIMIZEBOX |
                       WS_CAPTION | WS_BORDER | WS_SYSMENU | WS_THICKFRAME,
                       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
                       0,
                       MainMenu,
                       InstanceHandle,
                       0);
}

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   INT nCmdShow)
{
   InstanceHandle = hInstance;
   if((MainWindow = CreateMainWindow()) == (HWND )0)
   {
      MessageBox(0, "Failed to create MainWindow!", "Warning", MB_OK);
      return 0;
   }
   StatusBarWindow = CreateStatusWindow(WS_CHILD|WS_VISIBLE|WS_BORDER|SBARS_SIZEGRIP,
                                        "Ready",
                                        MainWindow,
                                        StatusBarId);

   SetStatusBarWidths(MainWindow);
   CreateToolBar(MainWindow);
   ShowWindow(MainWindow, SW_SHOW);
   SetStatusBarText("First",  0);
   SetStatusBarText("Middle", 1);
   SetStatusBarText("Right",  2);
   MSG Msg;
   while(GetMessage(&Msg, 0, 0, 0))
   {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
   }
   size_t i;
   for(i = 0; i < Objects.size(); i++)
      delete Objects[i];
   return Msg.wParam;
}