/* The original version of this program can be found at http://damb.dk */
#include <windows.h>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <iterator>

std::vector<std::string >FileData;  // This is the store for the text
const unsigned int LineHeight = 16;
unsigned int CharWidth;
unsigned int YOffset; // The current scroll offset
unsigned int CurrentLine, CurrentColumn;
char CurrentFileName[256];

enum CommandEnum
{
   FileOpenCmd = 1025,
   FileSaveCmd,
   FileCloseCmd,
   AboutCmd,
   DeleteCmd,
   BackSpaceCmd,
   EnterCmd,
   JoinLineCmd
};

ACCEL Accelerators[] =
{
   {FVIRTKEY, VK_DELETE, DeleteCmd},
   {FVIRTKEY, VK_BACK,  BackSpaceCmd},
   {FVIRTKEY, VK_RETURN,  EnterCmd},
   {FVIRTKEY|FCONTROL, 'J', JoinLineCmd}
};

HINSTANCE InstanceHandle;
HWND      MainWindow;
HFONT Font;

bool ReadFile(const char *aFileName)
{
   std::ifstream File(aFileName);
   if(!File)
      return false;
   std::string Line;
   while(std::getline(File, Line))
   {
      #ifdef __BORLANDC__
      if(!Line.empty() && Line[Line.size()] == 0)
      {
         Line.resize(Line.size() - 1);
      }
      #endif
      FileData.push_back(Line);
   }

   return true;
}

unsigned int GetWindowHeight()
{
   RECT R;
   GetClientRect(MainWindow, &R);
   return (unsigned int )(R.bottom - R.top);
}

unsigned int GetWindowWidth()
{
   RECT R;
   GetClientRect(MainWindow, &R);
   return (unsigned int )(R.right - R.left);
}

void SetScroll()
{
   int Height = GetWindowHeight();
   SCROLLINFO ScrollInfo;
   memset(&ScrollInfo, 0, sizeof(ScrollInfo));
   ScrollInfo.cbSize = sizeof(ScrollInfo);
   ScrollInfo.fMask = SIF_ALL;
   ScrollInfo.nPos = YOffset;
   ScrollInfo.nMax = FileData.size()*LineHeight;
   ScrollInfo.nPage = (Height/LineHeight)*LineHeight;
   SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
}

void DoPaint(HDC aDc)
{
   size_t a;
   HGDIOBJ OldFont = SelectObject(aDc, Font);
   size_t NumLines = (GetWindowHeight() + LineHeight - 1)/LineHeight;
   int FirstLine = YOffset/LineHeight;
   for(a = 0; a < NumLines && a + FirstLine < FileData.size(); a++)
      TextOut(aDc, 0, a*LineHeight, FileData[a + FirstLine].c_str(), FileData[a + FirstLine].size());
   SelectObject(aDc, OldFont);
}

void RedrawCurrentLine()
{
   HideCaret(MainWindow);
   HDC dc = GetDC(MainWindow);
   HGDIOBJ OldFont = SelectObject(dc, Font);
   COLORREF BackColor = GetBkColor(dc);
   HBRUSH Brush = CreateSolidBrush(BackColor);
   RECT Rect = {0, CurrentLine*LineHeight - YOffset, GetWindowWidth(), (CurrentLine + 1)*LineHeight};
   FillRect(dc, &Rect, Brush);
   TextOut(dc, 0, CurrentLine*LineHeight - YOffset, FileData[CurrentLine].c_str(), FileData[CurrentLine].size());
   SelectObject(dc, OldFont);
   DeleteObject(Brush);
   ReleaseDC(MainWindow, dc);
   SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
   ShowCaret(MainWindow);
}

void OnVScroll(int code, int pos)
{
   SCROLLINFO ScrollInfo;
   memset(&ScrollInfo, 0, sizeof(ScrollInfo));
   ScrollInfo.cbSize = sizeof(ScrollInfo);
   ScrollInfo.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
   GetScrollInfo(MainWindow, SB_VERT, &ScrollInfo);

   switch(code)
   {
   case SB_LINEUP:
      ScrollInfo.nPos -= LineHeight;
      break;
   case SB_LINEDOWN:
      ScrollInfo.nPos += LineHeight;
      break;
   case SB_PAGEDOWN:
      ScrollInfo.nPos += ScrollInfo.nPage;
      break;
   case SB_PAGEUP:
      ScrollInfo.nPos -= ScrollInfo.nPage;
      break;
   case SB_THUMBPOSITION:
      ScrollInfo.nPos = (pos/LineHeight)*LineHeight;
      break;
   case SB_ENDSCROLL:
      InvalidateRect(MainWindow, 0, TRUE);
   default:
      return;
   }
   if(ScrollInfo.nPos < 0)
      ScrollInfo.nPos = 0;
   else if(ScrollInfo.nPos > ScrollInfo.nMax)
      ScrollInfo.nPos = ScrollInfo.nMax;

   SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
   YOffset = ScrollInfo.nPos;
   // Make sure that the caret (CurrentLine) is within the visible window
   unsigned int Height = GetWindowHeight();
   if(CurrentLine*LineHeight < YOffset)
   {
      CurrentLine = (YOffset + LineHeight - 1)/LineHeight;
   }
   else if(CurrentLine*LineHeight > YOffset + Height)
   {
      CurrentLine = (ScrollInfo.nPos + Height)/LineHeight;
   }
   SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
}

void MakeVisible()
{  // Make sure that CurrentLine is within visible window, scroll as needed.
   unsigned int  Height = GetWindowHeight();

   if((CurrentLine + 2)*LineHeight > YOffset + Height)
   { // Caret past end window
      SCROLLINFO ScrollInfo;
      memset(&ScrollInfo, 0, sizeof(ScrollInfo));
      ScrollInfo.cbSize = sizeof(ScrollInfo);
      ScrollInfo.fMask = SIF_POS;
      GetScrollInfo(MainWindow, SB_VERT, &ScrollInfo);
      ScrollInfo.nPos = (((CurrentLine + 2)*LineHeight - Height)/LineHeight)*LineHeight;
      YOffset = ScrollInfo.nPos;
      SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
      InvalidateRect(MainWindow, 0, TRUE);
   }
   else if(CurrentLine*LineHeight < YOffset)
   { // Caret above top of window
      SCROLLINFO ScrollInfo;
      memset(&ScrollInfo, 0, sizeof(ScrollInfo));
      ScrollInfo.cbSize = sizeof(ScrollInfo);
      ScrollInfo.fMask = SIF_POS;
      GetScrollInfo(MainWindow, SB_VERT, &ScrollInfo);
      ScrollInfo.nPos = CurrentLine*LineHeight;
      YOffset = ScrollInfo.nPos;
      SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
      InvalidateRect(MainWindow, 0, TRUE);
   }
}

void OnKeyDown(int aKey)
{
   switch(aKey)
   {
   case VK_DOWN:
      if(CurrentLine < FileData.size() - 1)
      {
         CurrentLine++;
         MakeVisible();
         SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
      }
      break;
   case VK_UP:
      if(CurrentLine)
      {
         CurrentLine--;
         MakeVisible();
         SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
      }
      break;
   case VK_LEFT:
      if(CurrentColumn)
      {
         CurrentColumn--;
         MakeVisible();
         SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
      }
      break;
   case VK_RIGHT:
      unsigned int Width = GetWindowWidth()/CharWidth;
      if(CurrentColumn < Width - 1)
      {
          CurrentColumn++;
          MakeVisible();
          SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
      }
      break;
   }
}

void OnChar(int aChar)
{
   if(aChar < 32 || aChar >= 128) // Ignore not normal chars
      return;
   if(CurrentLine >= FileData.size())
      return;
   if(CurrentColumn <= FileData[CurrentLine].size())
   {
      FileData[CurrentLine].insert(FileData[CurrentLine].begin() + CurrentColumn, (char )aChar);
      CurrentColumn++;
      RedrawCurrentLine();
   }
}

void OnDelete()
{
   if(CurrentLine < FileData.size() && CurrentColumn < FileData[CurrentLine].size())
   {
      FileData[CurrentLine].erase(FileData[CurrentLine].begin() + CurrentColumn);
      RedrawCurrentLine();
   }
}

void OnBackspace()
{
   if(CurrentLine < FileData.size() && CurrentColumn && CurrentColumn <= FileData[CurrentLine].size())
   {
      CurrentColumn--;
      FileData[CurrentLine].erase(FileData[CurrentLine].begin() + CurrentColumn);
      RedrawCurrentLine();
   }
}

void OnEnter()
{
   if(CurrentLine >= FileData.size())
      return;
   std::string NewLine;
   if(CurrentColumn < FileData[CurrentLine].size())
   {  // Split the line in two
      NewLine = FileData[CurrentLine].substr(CurrentColumn);
      FileData[CurrentLine] = FileData[CurrentLine].substr(0, CurrentColumn);
   }
   CurrentLine++;
   FileData.insert(FileData.begin() + CurrentLine, NewLine);
   CurrentColumn = 0;
   SetScroll();
   MakeVisible();
   SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
   InvalidateRect(MainWindow, 0, TRUE);
}

void OnJoinLine()
{
   if(CurrentLine < FileData.size() - 1)
   {
      FileData[CurrentLine] += FileData[CurrentLine + 1];
      FileData.erase(FileData.begin() + CurrentLine + 1);
      InvalidateRect(MainWindow, 0, TRUE);
      SetScroll();
   }
}

void OnFileOpen()
{
   static char Filter[] = "txt (*.txt)\0*.txt\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 = "txt";

   if(GetOpenFileName(&OpenFilename))
   {
      FileData.clear();
      if(!ReadFile(CurrentFileName))
      {
         FileData.push_back("");
         CurrentFileName[0] = 0;
         std::string Msg = "Failed to open:\r\n";
         Msg += CurrentFileName;
         MessageBox(MainWindow, Msg.c_str(), "TextEditor", MB_OK | MB_ICONWARNING);
      }
      CurrentLine = 0;
      CurrentColumn = 0;
      YOffset = 0;

      SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
      SetScroll();
      InvalidateRect(MainWindow, 0, TRUE);
   }
}

void OnFileSave()
{
   if(CurrentFileName[0] == 0)
   {  // Need to Get A filename
      static char Filter[] = "txt (*.txt)\0*.txt\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 = 0;
      OpenFilename.lpstrDefExt = "txt";
      if(!GetSaveFileName(&OpenFilename))
      {
         CurrentFileName[0] = 0;
         return;
      }
   }
   std::ofstream File(CurrentFileName);
   if(!File)
   {
      std::string Msg = "Failed to open:\r\n";
      Msg += CurrentFileName;
      MessageBox(MainWindow, Msg.c_str(), "TextEditor", MB_OK | MB_ICONWARNING);
      CurrentFileName[0] = 0;
      return;
   }
   std::copy(FileData.begin(), FileData.end(), std::ostream_iterator<std::string>(File, "\n"));
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch (msg)
   {
   case WM_DESTROY:
      PostQuitMessage(0);
      break;
   case WM_CREATE:
   {
      MainWindow = hwnd;
      Font = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Courier");
      HDC Dc = GetDC(MainWindow);
      HGDIOBJ OldFont = SelectObject(Dc, Font);
      TEXTMETRIC TextMetric;
      GetTextMetrics(Dc, &TextMetric);
      CharWidth = TextMetric.tmAveCharWidth;
      SelectObject(Dc, OldFont);
      ReleaseDC(MainWindow, Dc);
      break;
   }
   case WM_CHAR:
      OnChar(wParam);
      break;
   case WM_COMMAND:
      switch(LOWORD(wParam))
      {
      case DeleteCmd:
         OnDelete();
         break;
      case BackSpaceCmd:
         OnBackspace();
         break;
      case EnterCmd:
         OnEnter();
         break;
      case JoinLineCmd:
         OnJoinLine();
         break;
      case FileCloseCmd:
         PostMessage(MainWindow, WM_QUIT, 0, 0);
         break;
      case FileOpenCmd:
         OnFileOpen();
         break;
      case FileSaveCmd:
         OnFileSave();
         break;
      case AboutCmd:
         MessageBox(MainWindow, "TextEditor\r\nCopyright Bertel Brander 2005", "TextEditor", MB_OK);
         break;
      }
      break;
   case WM_SETFOCUS:
      CreateCaret(MainWindow, 0, 2, LineHeight);
      SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
      ShowCaret(hwnd);
      break;
   case WM_KEYDOWN:
      OnKeyDown(wParam);
      break;
   case WM_KILLFOCUS:
      DestroyCaret();
      HideCaret(hwnd);
      break;
   case WM_SIZE:
      SetScroll();
      break;
   case WM_VSCROLL:
      OnVScroll(LOWORD(wParam), HIWORD(wParam));
      break;
   case WM_PAINT:
      HDC dc;
      PAINTSTRUCT PaintStruct;
      dc = BeginPaint(hwnd, &PaintStruct);
      DoPaint(dc);
      EndPaint(hwnd, &PaintStruct);
      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 = "TextEditorWndClass";
   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_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("TextEditorWndClass",
                       "Simple-Editor",
                       WS_MINIMIZEBOX | WS_VISIBLE |
                       WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_MAXIMIZEBOX |
                       WS_CAPTION | WS_BORDER | WS_SYSMENU | WS_THICKFRAME | WS_VSCROLL,
                       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
                       0,
                       MainMenu,
                       InstanceHandle,
                       0);
}

int WINAPI WinMain(HINSTANCE Instance,
                   HINSTANCE,
                   LPSTR CmdLine,
                   INT)
{
   InstanceHandle = Instance;
   if(CmdLine[0])
   {
      if(!ReadFile(CmdLine))
      {
         MessageBox(0, "Failed To read file", "TextEditor", MB_OK);
         FileData.push_back(""); // Need at least one empty line
      }
      else
      {
         strcpy(CurrentFileName, CmdLine);
      }
   }
   else
   {
      FileData.push_back(""); // Need at least one empty line
   }
   if((MainWindow = CreateMainWindow()) == (HWND )0)
   {
      MessageBox(0, "Failed to create MainWindow!", "TextEditor", MB_OK);
      return 0;
   }
   HACCEL AcceleratorHandle = CreateAcceleratorTable(Accelerators, sizeof(Accelerators)/sizeof(Accelerators[0]));
   ShowWindow(MainWindow, SW_SHOW);
   MSG Msg;
   while(GetMessage(&Msg, 0, 0, 0))
   {
      if(!TranslateAccelerator(MainWindow, AcceleratorHandle, &Msg))
      {
         TranslateMessage(&Msg);
         DispatchMessage(&Msg);
      }
   }
   return Msg.wParam;
}