//
// CBaseClassWindow.cpp
//
// (c)Copyright 2004 Steven J. Eschweiler. All Rights Reserved.
//

#include "cbaseclasswindow.h"
#include <windows.h>
#include <iostream>
#include <assert.h>

// Internal helper functions - Required to handle window procedure - Must be global!
LRESULT CALLBACK CBaseClassWindowWndProc2(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK CBaseClassWindowWndProc(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam);

// Internal helper variables - Required to handle window procedure - Must be global!
CBaseClassWindow *g_pWindowBaseClass;

// Initialize static class variables
int CBaseClassWindow::m_nNumInstances=0;

CBaseClassWindow::CBaseClassWindow(void)
{
   m_hwnd = NULL;
   m_hwndParent = NULL;
   m_nNumChildren = 0;
   m_nControlID = 0;
}

CBaseClassWindow::~CBaseClassWindow(void)
{
}

bool CBaseClassWindow::Init(DWORD dwExStyle,LPCTSTR lpClassName,LPCTSTR lpWindowName,DWORD dwStyle,int x,int y,int nWidth,int nHeight,HMENU hMenu,
                            COLORREF clrBackground,HICON hIcon,HICON hIconSm,bool bRegisterWindowClass)
{
   WNDCLASSEX wcx;

   // Make sure it's not already created
   assert(m_hwnd==NULL);

   // Force it to < 256 and test it
   if (lpClassName)
      assert(strlen(lpClassName)<254);
   if (lpWindowName)
      assert(strlen(lpWindowName)<254);

   // Initialize this
   /*
   if (BGRed==-1 || BGGreen==-1 || BGBlue==-1)
   {
   m_BGRed = 236;
   m_BGGreen = 233;
   m_BGBlue = 216;
   }
   else
   {
   m_BGRed = BGRed;
   m_BGGreen = BGGreen;
   m_BGBlue = BGBlue;
   }
   */
   m_clrBackground = clrBackground;

   m_hBrushStatic = static_cast<HBRUSH>(CreateSolidBrush(clrBackground));
   m_bRegisterWindowClass=bRegisterWindowClass;
   m_dwExStyle=dwExStyle;
   if (lpClassName)
      strcpy_s(m_lpClassName,lpClassName);
   else
      m_lpClassName[0]=0;
   if (lpWindowName)
      strcpy_s(m_lpWindowName,lpWindowName);
   else
      m_lpWindowName[0]=0;
   m_dwStyle=dwStyle;
   m_x=x;
   m_y=y;
   m_width=nWidth;
   m_height=nHeight;
   /*
   hMenu 
   [in] Handle to a menu, or specifies a child-window 
   identifier, depending on the window style. For an 
   overlapped or pop-up window, hMenu identifies the 
   menu to be used with the window; it can be NULL if
   the class menu is to be used. For a child window, 
   hMenu specifies the child-window identifier, an 
   integer value used by a dialog box control to 
   notify its parent about events. The application
   determines the child-window identifier; it must be
   unique for all child windows with the same parent
   window. 
   */
   m_hMenu=hMenu;

   // See: http://www.codecomments.com/archive371-2005-11-708667.html
   // You can avoid this warning but 64 bit windows would uses 64 bit pointers
   // m_nControlID = static_cast<UINT>(reinterpret_cast<intptr_t>(m_hMenu));
   m_nControlID = reinterpret_cast<UINT>(hMenu);
   if (m_nControlID==WM_APP)
   {
      char szTmp[256];
      sprintf_s(szTmp,"%d-%d-%d-%s-%s-%d-%d-%d-%d-%d{1990BE72-30C7-4bac-8DF9-06DEB04591DB}",rand(),m_dwExStyle,m_dwStyle,m_lpClassName,m_lpWindowName,m_x,m_y,m_width,m_height,m_hBrushStatic);
      m_nControlID = ::RegisterWindowMessage(szTmp);
      m_hMenu = reinterpret_cast<HMENU>(m_nControlID);
   }

   // Create a unique class name, if needed
   m_nNumInstances++;
   if (lpClassName==NULL)
      sprintf_s(m_lpClassName,"CBaseClassWindow%d",m_nNumInstances);

   // Now save an uppercase copy of class name
   m_szClassNameUppercase=m_lpClassName;
   //m_szClassNameUppercase.MakeUpper();
   m_szClassNameUppercase = m_SM.ToUpper(m_szClassNameUppercase);

   // Register window class
   if (m_bRegisterWindowClass)
   {
      wcx.cbSize = sizeof(wcx);
      wcx.lpszClassName = m_lpClassName;
      wcx.hInstance = GetModuleHandle(NULL);
      wcx.lpfnWndProc = (WNDPROC)CBaseClassWindowWndProc;
      wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
      // wcx.hCursor = LoadCursor(NULL, IDC_APPSTARTING);
      wcx.hIcon = hIcon;
      wcx.lpszMenuName = NULL;
      wcx.hbrBackground = m_hBrushStatic;
      wcx.style = NULL;
      wcx.cbClsExtra = 0;
      wcx.cbWndExtra = 0;
      wcx.hIconSm = hIconSm;
      ATOM atom = RegisterClassEx(&wcx);
      if (atom==0)
         return false;
      assert(atom!=0);
   }

   return true;
}

HWND CBaseClassWindow::Create(HWND hwndParent)
{
   // Make sure it's not already created
   assert(m_hwnd==NULL);

   // Save parent info
   m_hwndParent = hwndParent;

   // Our WndProc solution
   g_pWindowBaseClass = this;

   // Create the window now
   m_hwnd = CreateWindowEx( m_dwExStyle,
      m_lpClassName,
      m_lpWindowName,
      m_dwStyle,
      m_x,
      m_y,
      m_width,
      m_height,
      m_hwndParent,
      m_hMenu,
      GetModuleHandle(NULL),
      NULL );

   // A little debug code
   assert(m_hwnd!=NULL);


   if (!m_bRegisterWindowClass)
   {
      // Save old WndProc and assign new
      m_OriginalWndProc = (WNDPROC)SetWindowLongPtr(m_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(CBaseClassWindowWndProc));
   }

   // If member OnCreate() exists, call it now...
   // This is a virtual function so the derived class member version
   // of OnCreate() will be called if it exists!
   // So far, our checkbox class and our TreeView class need this...
   OnCreate();

   return m_hwnd;
}

// If you create (derive) this virtual member function in your derived class, you must call this base class version!
LRESULT CBaseClassWindow::WndProc(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{
   LRESULT lResultVal;
   int nChildIndexTabFrom,nChildIndexTabTo,nDefaultChildButton;
   bool bTabToNext=false;
   bool bTabToPrevious=false;
   bool bPressDefaultButton=false;

   if (m_bRegisterWindowClass)
   {
      // Handle any child WS_TABSTOP controls first
      for (nChildIndexTabFrom=0; nChildIndexTabFrom<m_nNumChildren; nChildIndexTabFrom++)
      {
         if (mMsg==m_cptrChildWindow[nChildIndexTabFrom]->GetControlId())
         {
            // OK, a control has sent one of our custom messages
            if (lParam==WM_KEYDOWN && wParam==VK_TAB)
            {
               // It was a request to TAB to the next control
               if (GetKeyState(VK_SHIFT)&0x8000)
                  bTabToPrevious=true;
               else
                  bTabToNext=true;
               break;
            }
            if (lParam==WM_KEYDOWN && wParam==VK_RETURN)
            {
               // Press the default button... Only one button should be the
               // default. Note that we just simulate a press on the first
               // default button we find.
               bPressDefaultButton=true;
               break;
            }
         }
      }
      if (bTabToNext)
      {
         nChildIndexTabTo = (nChildIndexTabFrom+1)%m_nNumChildren;
         do
         {
            if (m_cptrChildWindow[nChildIndexTabTo]->m_dwStyle&WS_TABSTOP)
            {
               break;
            }
            nChildIndexTabTo++;
            nChildIndexTabTo%=m_nNumChildren;
         } while (nChildIndexTabTo!=nChildIndexTabFrom);			
         m_cptrChildWindow[nChildIndexTabTo]->SetFocus();
         //SetFocus(m_cptrChildWindow[nChildIndexTabTo]->GetHwnd());
      }
      else if (bTabToPrevious)
      {
         nChildIndexTabTo = nChildIndexTabFrom-1;
         if (nChildIndexTabTo<0)
            nChildIndexTabTo=m_nNumChildren-1;
         do
         {
            if (m_cptrChildWindow[nChildIndexTabTo]->m_dwStyle&WS_TABSTOP)
            {
               break;
            }
            nChildIndexTabTo--;
            if (nChildIndexTabTo<0)
               nChildIndexTabTo=m_nNumChildren-1;
         } while (nChildIndexTabTo!=nChildIndexTabFrom);			
         //SetFocus(m_cptrChildWindow[nChildIndexTabTo]->GetHwnd());
         m_cptrChildWindow[nChildIndexTabTo]->SetFocus();
      }
      else if (bPressDefaultButton)
      {
         for (nDefaultChildButton=0; nDefaultChildButton<m_nNumChildren; nDefaultChildButton++)
         {
            if ( m_cptrChildWindow[nDefaultChildButton]->m_szClassNameUppercase=="BUTTON" &&
               (m_cptrChildWindow[nDefaultChildButton]->m_dwStyle&BS_DEFPUSHBUTTON) )
            {
               //MessageBox(hwnd,"Hello","JK",MB_OK);
               PostMessage(hwnd,WM_COMMAND,MAKEWPARAM(m_cptrChildWindow[nDefaultChildButton]->GetControlId(),1),reinterpret_cast<LPARAM>(m_cptrChildWindow[nDefaultChildButton]->GetHwnd()));
               break;
            }
         }
      }

      // Most likely a window. Either way, this is safe!
      lResultVal = DefWindowProc(hwnd, mMsg, wParam, lParam);
   }
   else
   {
      // Most likely a control. Either way, this is safe!
      lResultVal = CallWindowProc(this->m_OriginalWndProc, hwnd, mMsg, wParam, lParam);
   }

   return lResultVal;
}

void CBaseClassWindow::OnCreate()
{
}

void CBaseClassWindow::AttachChild(CBaseClassWindow *cptrChildWindow)
{
   assert(m_nNumChildren<m_nMaxChildren);

   m_cptrChildWindow[m_nNumChildren] = cptrChildWindow;
   m_nNumChildren++;	
}

void CBaseClassWindow::CreateChildren(HWND hwndParent)
{
   if (m_nNumChildren>0)
   {
      for (int LoopVar=0; LoopVar<m_nNumChildren; LoopVar++)
      {
         m_cptrChildWindow[LoopVar]->Create(hwndParent);
         // Now save a copy of our parent window
         m_cptrChildWindow[LoopVar]->m_pWndParent = this;
      }
   }	
}

LRESULT CALLBACK CBaseClassWindowWndProc(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{
   // See: http://www.rpi.edu/~pudeyo/articles/wndproc/
   // And: http://www.gamedev.net/reference/articles/article1901.asp
   /*
   switch (mMsg)
   {
   case WM_NCCREATE:
   {
   LPCREATESTRUCT cs;
   cs = reinterpret_cast<LPCREATESTRUCT>(lParam);
   // cs->lpCreateParams is the "this" pointer from CButtonClass::Create->CreateWindowEx()
   SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cs->lpCreateParams) );
   break;
   }
   case WM_GETMINMAXINFO:
   {
   // GUESS WHAT! WM_GETMINMAXINFO is the first function called when a 
   // main window is created causing the program to crash on startup!!!
   // This is because WM_NCCREATE is not called first and also 
   // cs->lpCreateParams is NOT the "this" pointer from 
   // CButtonClass::Create->CreateWindowEx() and so we have no way of knowing 
   // what WndProc to call!!!

   // So..........
   // We will use an alternate method, described here:
   // http://www.rpi.edu/~pudeyo/articles/wndproc/

   return (DefWindowProc(hwnd, mMsg, wParam, lParam));
   break;
   }
   }

   CBaseClassWindow *pcwbc = reinterpret_cast<CBaseClassWindow *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
   return pcwbc->WndProc(hwnd, mMsg, wParam, lParam);
   */
   // Stash global Window pointer into per-window data area

   SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(g_pWindowBaseClass));
   // Reset the window message handler
   SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(CBaseClassWindowWndProc2));
   //g_pWindowBaseClass->m_OriginalWndProc = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(CBaseClassWindowWndProc2));

   // Dispatch first message to the member message handler
   return CBaseClassWindowWndProc2(hwnd, mMsg, wParam, lParam);
}

LRESULT CBaseClassWindow::OnCtlColorStatic(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{
   // WM_CTLCOLORSTATIC

   // This is what our radio & checkbox buttons use for background colors!!!

   // You can actually process this message to change the text color. This
   // would be useful for highlighting our text when hovering over it...
   // We should create a class for this...
   return reinterpret_cast<LRESULT>(m_hBrushStatic);
}

void CBaseClassWindow::OnDestroy(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{
   // WM_DESTROY
   DeleteObject(m_hBrushStatic);
}

LRESULT CALLBACK CBaseClassWindowWndProc2(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{
   // Get a window pointer associated with this window
   CBaseClassWindow *w = reinterpret_cast<CBaseClassWindow *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
   // It should be valid, assert so
   assert(w);

   // Redirect messages to the window procedure of the associated window
   LRESULT lResult = w->WndProc(hwnd, mMsg, wParam, lParam);

   // We could put this before or after the call to the class WndProc() function
   if (mMsg == WM_DESTROY)
      w->OnDestroy(hwnd, mMsg, wParam, lParam);

   // This MUST come _after_ we call class WndProc (w->WndProc() above)
   if (mMsg == WM_CTLCOLORSTATIC)
      return w->OnCtlColorStatic(hwnd, mMsg, wParam, lParam);

   return lResult;
}

void CBaseClassWindow::Show(void)
{
   ShowWindow(m_hwnd,SW_SHOW);
}

void CBaseClassWindow::Hide(void)
{
   ShowWindow(m_hwnd,SW_HIDE);
}

void CBaseClassWindow::Minimize(void)
{
   ShowWindow(m_hwnd,SW_MINIMIZE);
}

void CBaseClassWindow::Maximize(void)
{
   ShowWindow(m_hwnd,SW_MAXIMIZE);
}

void CBaseClassWindow::Restore(void)
{
   ShowWindow(m_hwnd,SW_RESTORE);
}

void CBaseClassWindow::Update(void)
{
   UpdateWindow(m_hwnd);
}

void CBaseClassWindow::Invalidate(LPRECT lpRect)
{
   InvalidateRect(m_hwnd,lpRect,TRUE);
}

int CBaseClassWindow::GetX(void)
{
   /*
   RECT rc;
   GetClientRect(m_hwnd,&rc);
   MapWindowPoints(m_hwnd,m_hwndParent,reinterpret_cast<LPPOINT>(&rc),2);
   return rc.left;
   */
   // This following method is preferred and is safe as long as we keep 
   // it updated - For example, in the move functions below...
   // It is preferred because we can use it to layout our window even
   // when the window has not been created yet but only initialized.
   return m_x;
}

int CBaseClassWindow::GetY(void)
{
   /*
   RECT rc;
   GetClientRect(m_hwnd,&rc);
   MapWindowPoints(m_hwnd,m_hwndParent,reinterpret_cast<LPPOINT>(&rc),2);
   return rc.top;
   */
   // This following method is preferred and is safe as long as we keep 
   // it updated - For example, in the move functions below...
   // It is preferred because we can use it to layout our window even
   // when the window has not been created yet but only initialized.
   return m_y;
}

int CBaseClassWindow::GetWidth(void)
{
   /*
   RECT rc;
   GetClientRect(m_hwnd,&rc);
   return rc.right;
   */
   // This following method is preferred and is safe as long as we keep 
   // it updated - For example, in the move functions below...
   // It is preferred because we can use it to layout our window even
   // when the window has not been created yet but only initialized.
   return m_width;
}

int CBaseClassWindow::GetHeight(void)
{
   /*
   RECT rc;
   GetClientRect(m_hwnd,&rc);
   return rc.bottom;
   */
   // This following method is preferred and is safe as long as we keep 
   // it updated - For example, in the move functions below...
   // It is preferred because we can use it to layout our window even
   // when the window has not been created yet but only initialized.
   return m_height;
}

void CBaseClassWindow::Move(int x,int y)
{
   m_x = x;
   m_y = y;
   MoveWindow(m_hwnd,x,y,m_width,m_height,TRUE);
}

void CBaseClassWindow::Move(int x,int y,int width,int height)
{
   m_x = x;
   m_y = y;
   m_width = width;
   m_height = height;
   MoveWindow(m_hwnd,x,y,m_width,m_height,TRUE);
}

void CBaseClassWindow::Move(int x,int y,int width,int height,BOOL bRepaint)
{
   m_x = x;
   m_y = y;
   m_width = width;
   m_height = height;
   MoveWindow(m_hwnd,x,y,m_width,m_height,bRepaint);
}


void CBaseClassWindow::SetClassCursor(HCURSOR hCursor)
{
   SetClassLongPtr(m_hwnd,GCLP_HCURSOR,reinterpret_cast<LONG_PTR>(hCursor));
   //this->Invalidate();
}


bool CBaseClassWindow::Enable(bool bEnable)
{
   /*
   if (bEnable==false)
   {
   //SetClassLongPtr(m_hwnd,GCLP_HCURSOR,(LONG_PTR)
   //SetClassLongPtr(m_hwnd,GCLP_HCURSOR,reinterpret_cast<LONG_PTR>(LoadCursor(NULL, IDC_APPSTARTING)));
   SetClassLong(m_hwnd,GCL_HCURSOR,reinterpret_cast<LONG>(LoadCursor(NULL, IDC_APPSTARTING)));
   SetClassLongPtr(m_hwnd,GCLP_HCURSOR,reinterpret_cast<LONG_PTR>(LoadCursor(NULL, IDC_APPSTARTING)));
   //SetClassLong(m_hwnd,GCLP_HCURSOR,reinterpret_cast<LONG>(LoadCursor(NULL, IDC_APPSTARTING)));
   //SetCursor(LoadCursor(NULL, IDC_APPSTARTING));
   //SetClassLongPtr(m_hwnd,GCLP_HCURSOR,NULL);
   }
   //return true;
   */


   return static_cast<bool>(EnableWindow(m_hwnd,static_cast<BOOL>(bEnable)));
}

bool CBaseClassWindow::IsEnabled(void)
{
   return static_cast<bool>(IsWindowEnabled(m_hwnd));
}

HWND CBaseClassWindow::GetHwnd(void)
{
   return m_hwnd;
}

HWND CBaseClassWindow::GetHwndParent(void)
{
   return m_hwndParent;
}

HWND CBaseClassWindow::SetFocus()
{
   return ::SetFocus(m_hwnd);
}

COLORREF CBaseClassWindow::GetBkColor()
{
   return m_clrBackground;
}

void CBaseClassWindow::SetFont(HFONT hFont,bool bRedraw)
{
   //HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
   SendMessage(m_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(hFont), MAKELPARAM(static_cast<BOOL>(bRedraw), 0));
}

UINT CBaseClassWindow::GetControlId()
{
   return m_nControlID;
}

std::string CBaseClassWindow::GetText(bool bTrimmed,bool bCapped,int nCappedLength,bool bCapitalizeFirstLetter)
{
   char ch;
   int szLength=GetWindowTextLength(m_hwnd)+1; // +1 for terminating NULL character
   if (bCapped)
   {
      if (szLength>nCappedLength)
         szLength = nCappedLength+1;
   }
   char *pszText = new char[szLength];
   GetWindowText(m_hwnd,pszText,szLength);
   std::string strText=pszText;
   delete pszText;
   if (bTrimmed)
      strText = m_SM.Trim(strText);

   if (bCapitalizeFirstLetter)
   {
      if (strText.length() > 0)
      {
         ch = strText.at(0);
         if (ch>='a' && ch<='z')
            strText[0] = ch-('a'-'A');
      }
   }
   //if (bCapitalizeAllFirstLetters)
   //{
   //}

   return strText;
}

void CBaseClassWindow::ReadOnly(bool bReadOnly)
{
   // this style after the control has been created, use the EM_SETREADONLY message. 
   SendMessage(m_hwnd,EM_SETREADONLY,static_cast<WPARAM>(bReadOnly),0);
}