#include "comline.hpp"
//#include <exception>
#include <stdio.h>
#include "comline.h"

#define COMLINE_WINDOW_NAME "Command Window"
#define COMLINE_WINDOW_CLASS_NAME "COMLINEWindowClass"

namespace NCOMLINE
{
	const DWORD c_dwWindowedStyle = WS_OVERLAPPED|WS_VSCROLL;
	const DWORD c_dwWindowedStyleEx = WS_EX_WINDOWEDGE;
	const int c_FontSize = 12;
	const int c_LineSpace = 2;
	const int c_FlashTimerID = 37;
	const int c_FlashElapse = 125;

	const int c_ServerTimerID = 38;
	const int c_ServerTimerElapse = 1000;

	CCom_String n_Commands[] =        // order is VERY important, see the ExecuteCommand function
	{
		"dumpfile",
		"exit",
		"attach",
		"detach",
		"killserver"
	};
//	CCommandLineClient *n_CurClient;
};

int CCommandLineClient::m_WndClassRefCount = 0;
using namespace NCOMLINE;

void CCommandLineClient::PushLine (const LineData& data)
{
	if (m_NumLines == m_SizeLines)
	{
		int i;
		LineData *temp;

		temp = new LineData[m_SizeLines+50];
		for (i=0;i<m_NumLines;i++)
		{
			temp[i] = m_Lines[i];
		}
		delete[] m_Lines;
		m_Lines = temp;
		m_SizeLines+=50;
	}
	m_Lines[m_NumLines] = data;
	m_NumLines++;


	LineData *iter;

	iter = &(m_Lines[m_TopDisplay]);
	int count=0;
	int i;
	for (i=m_TopDisplay;i<m_NumLines;i++)
	{
		count++;
		iter++;
	}
	if (count > m_DisplayLines)
	{
		int min,max;
		m_TopDisplay++;
		GetScrollRange (m_Window,SB_VERT,&min,&max);
		SetScrollRange (m_Window,SB_VERT,0,max+1,FALSE);
	}
}

CCommandLineClient::CCommandLineClient (HINSTANCE inst)
{
	m_Instance = inst;
	m_Attached = false;

	if (!m_WndClassRefCount)
	{
		WNDCLASS rootclass;

		rootclass.style = CS_OWNDC|CS_DBLCLKS;
		rootclass.lpfnWndProc = CommandLineWndProc;
		rootclass.cbClsExtra = 0;
		rootclass.cbWndExtra = sizeof (DWORD);
		rootclass.hInstance = m_Instance;
		rootclass.hIcon = LoadIcon (NULL,MAKEINTRESOURCE (IDI_APPLICATION));
		rootclass.hCursor = LoadCursor (NULL,MAKEINTRESOURCE (IDC_ARROW));
		rootclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
		rootclass.lpszMenuName = NULL;
		rootclass.lpszClassName = COMLINE_WINDOW_CLASS_NAME;

		if (!RegisterClass (&rootclass))						// register the class for the command line window
			return;
	}
	m_WndClassRefCount++;
	m_Window = CreateWindowEx (c_dwWindowedStyleEx,COMLINE_WINDOW_CLASS_NAME,COMLINE_WINDOW_NAME,c_dwWindowedStyle,
								CW_USEDEFAULT,CW_USEDEFAULT,80*12,(50*c_FontSize)+10,NULL,NULL,m_Instance,this);
	if (!m_Window)
		return;
	SetScrollRange (m_Window,SB_VERT,0,1,FALSE);
	SetWindowLong (m_Window,GWL_USERDATA,(LONG) this);
	SetTimer (m_Window,c_FlashTimerID,c_FlashElapse,NULL);
	SetTimer (m_Window,c_ServerTimerID,c_ServerTimerElapse,NULL);

	m_Font = CreateFont (-c_FontSize,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,
								OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,
								DEFAULT_PITCH+FF_DONTCARE,"Courier");

	m_CursorPen = CreatePen (PS_SOLID,1,RGB (0,0,0));
	m_ShowCursor = true;
	m_CursorLoc.x = 5;
	m_CursorLoc.y = 1;
	m_CursorPos = 0;
	m_CurrentLine = "";
	m_Lines = new NCOMLINE::LineData[50];
	m_NumLines = 0;
	m_SizeLines = 50;
//	m_Lines.clear ();
	m_TopDisplay = 0;
	PushLine (LineData ("Command Line interface started",LINE_OUTPUT));
	m_CurLineColor = RGB (0,0,0 );
	m_CommandColor = RGB (128,0,0);
	m_ArgColor = RGB (0,128,0);
	m_OutColor = RGB (0,0,255);
//	NCOMLINE::n_CurClient = this;

}

CCommandLineClient::~CCommandLineClient (void)
{
	LineData data ("detach all",LINE_COMMAND);
	ParseLine (data);

	if (m_Attached)
		DetachServer (data);
	DeleteObject (m_Font);
	DeleteObject (m_CursorPen);
	m_WndClassRefCount--;
	if (!m_WndClassRefCount)
		UnregisterClass (COMLINE_WINDOW_CLASS_NAME,m_Instance);
}

RPC_STATUS CCommandLineClient::AttachServer (NCOMLINE::LineData& data)
{
	if (m_Attached)
	{
		LineData fred ("detach all",LINE_COMMAND);
		ParseLine (fred);
		DetachServer (fred);
	}
	char compname[255];
	RPC_STATUS status;
	unsigned char * pszUuid             = (unsigned char *) NULL;
//  unsigned char * pszProtocolSequence = (unsigned char *) "ncalrpc";
//  unsigned char * pszEndpoint         = (unsigned char *) "ComLine";

//  unsigned char * pszProtocolSequence = (unsigned char *) "ncacn_ip_tcp";
//  unsigned char * pszEndpoint         = (unsigned char *) "morfane[1404]";
  unsigned char * pszNetworkAddress   = (unsigned char *) NULL;
  unsigned char * pszOptions          = (unsigned char *) NULL;
/*
	pszStringBinding = (unsigned char *) NULL;
  status = RpCCom_StringBindingCompose(pszUuid,pszProtocolSequence,pszNetworkAddress,pszEndpoint,pszOptions,&pszStringBinding);
  if (status)
		return status;
*/
	if (data.m_NumArgs == 1)
	{
		sprintf ((char *) pszStringBinding,"ncalrpc:[ComLine]");
	}
	else
	{
		CCom_String name;

		name = data.m_Data.Mid (data.m_ArgBreak[0]+1,data.m_ArgBreak[1]-data.m_ArgBreak[0]+1);
		strcpy (compname,(LPCTSTR) name);
		sprintf ((char *) pszStringBinding,"ncacn_ip_tcp:%s[1404]",compname);
	}
//	pszStringBinding = (unsigned char *) "ncacn_ip_tcp:209.20.251.178[1404]";

  /* Set the binding handle that will be used to bind to the server. */
  status = RpcBindingFromStringBinding(pszStringBinding,&comline_IfHandle);
  if (status)
		return status;
	m_Attached = true;
	return 0;
}

RPC_STATUS CCommandLineClient::DetachServer (NCOMLINE::LineData& data)
{
	RPC_STATUS status;

	m_Attached = false;

#if 0
  status = RpCCom_StringFree(&pszStringBinding);  // remote calls done; unbind
  if (status)
	  return (status);
#endif

	pszStringBinding[0] = 0;

  status = RpcBindingFree(&comline_IfHandle);  // remote calls done; unbind
  if (status)
		return (status);
	return 0;

}

void CCommandLineClient::ListServers (void)
{
}

void CCommandLineClient::ShowWindow (void)
{
	assert (m_Window);

	::ShowWindow (m_Window,SW_SHOW);

	GetClientRect (m_Window,&m_WindowRect);
	m_DisplayLines = (m_WindowRect.bottom - m_WindowRect.top) / (c_FontSize+c_LineSpace);
}
void CCommandLineClient::HideWindow (void)
{
	assert (m_Window);
	::ShowWindow (m_Window,SW_HIDE);
}

bool CCommandLineClient::DumpFile (NCOMLINE::LineData& data)
{
	CCom_String filename;

	if (data.m_NumArgs < 2)
		return false;
	filename = data.m_Data.Left (data.m_ArgBreak[1]);
	filename = filename.Right (data.m_ArgBreak[1] - data.m_ComBreak-1);
	return DumpFile (filename);
}

bool CCommandLineClient::DumpFile (const CCom_String& filename)
{
	FILE *thefile;
	CCom_String temp;
	LineData *iter;
	int i;

	thefile = fopen (filename,"w");
	if (!thefile)
		return false;

	iter = m_Lines;
	for (i=0;i<m_NumLines;i++)
	{
		fprintf (thefile,(LPCTSTR) iter->m_Data);
		fprintf (thefile,"\n");
		iter++;
	}
	fprintf (thefile,(LPCTSTR) m_CurrentLine);
	temp = "Data dumped to file ";
	temp += filename;
	PushLine ((LPCTSTR) temp);

	if (!fclose (thefile))
		return false;
	return true;
}

void CCommandLineClient::DumpToFile (const char *filename)
{
	DumpFile (filename);
}

void CCommandLineClient::PushLine (const char *line)
{
	LineData data(line,LINE_OUTPUT);

	PushLine (data);
	InvalidateRect (m_Window,NULL,TRUE);
}

void CCommandLineClient::ExecuteLine (const char *line)
{
	LineData data(line,LINE_COMMAND);
	ParseLine (data);
	PushLine (data);
	ExecuteLine (data);
}

void CCommandLineClient::KillServer (NCOMLINE::LineData& data)
{
	unsigned long ulCode;

	m_Attached = false;

	RpcTryExcept
	{
		Shutdown();  // shut down the server side
	}
	RpcExcept(1)
	{
			ulCode = RpcExceptionCode();
	}
	RpcEndExcept
}

void CCommandLineClient::ExecuteLine (LineData& data)
{
	int i,count;
	CCom_String com;
	bool flag = false;

	com = data.m_Data.Left (data.m_ComBreak);
	count = sizeof (n_Commands) /sizeof (CCom_String);
	for (i=0;i<count;i++)
	{
		if (com == n_Commands[i])
		{
			switch (i)
			{
			case 0:					//dumpfile
					DumpFile (data);
					flag = true;
				break;
			case 1:					// exit
				PostQuitMessage (0);
				flag = true;
				break;
			case 2:					// attach
				AttachServer (data);
				flag = true;
				break;
			case 3:					//detach
				DetachServer (data);
				flag = true;
				break;
			case 4:					//kill server
				KillServer (data);
				flag = true;
				break;
			}
		}
	}
	if (!flag)
	{
		if (m_Attached)
		{
			try
			{
			::ExecuteLine ((const unsigned char *) ((LPCTSTR) data.m_Data));
			}
			catch (...)
			{
				LineData data ("detach all",LINE_COMMAND);
				ParseLine (data);
				DetachServer (data);
				PushLine ("Error: Had to detach from server");
			}

		}
	}
}

void CCommandLineClient::ParseLine (LineData& data)
{
	char *buf;
	int i,size;
	bool ignore;

	ignore = false;
	data.m_Data.TrimLeft ();
	data.m_ComBreak = -1;
	size = data.m_Data.GetLength ();
	buf = data.m_Data.GetBuffer (0);
	for (i=0;i<size;i++,buf++)
	{
		if ((*buf == ' ') && !ignore)
		{
			data.m_ArgBreak[data.m_NumArgs] = i;
			data.m_NumArgs++;
		}
		else if (*buf == '\"')
		{
			ignore = !ignore;
		}
	}
	data.m_Data.ReleaseBuffer ();
	data.m_ArgBreak[data.m_NumArgs] = data.m_Data.GetLength ();
	data.m_NumArgs++;
	data.m_ComBreak = data.m_ArgBreak[0];
}

LRESULT CCommandLineClient::WndProc (HWND thewnd,UINT themsg,WPARAM wParam,LPARAM lParam)
{
	switch (themsg)
	{
	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HPEN temppen;
			HFONT oldfont;
			SIZE TextSize;

			BeginPaint (thewnd,&ps);

			int curx,cury;
			LineData *iter;
			int i;

			SetTextAlign (ps.hdc,TA_LEFT+TA_TOP);
			SetTextColor (ps.hdc,m_CommandColor);
			SetBkColor (ps.hdc,RGB (0,0,0));
			SetBkMode (ps.hdc,TRANSPARENT);
			oldfont = (HFONT) SelectObject (ps.hdc,m_Font);


			curx = 0;
			cury = 0;
			iter = &(m_Lines[m_TopDisplay]);
			for (i=m_TopDisplay;i<m_NumLines;i++)
			{
				if (iter->m_Type == LINE_OUTPUT)
				{
					SetTextColor (ps.hdc,m_OutColor);
					TextOut (ps.hdc,curx,cury,(LPCTSTR)iter->m_Data,iter->m_Data.GetLength ());
				}
				else if (iter->m_Type == LINE_COMMAND)
				{
					char *buf;

					buf = iter->m_Data.GetBuffer (0);
					SetTextColor (ps.hdc,m_CommandColor);
					TextOut (ps.hdc,curx,cury,(LPCTSTR)buf,iter->m_ComBreak);
					if ((iter->m_Data.GetLength () - iter->m_ComBreak) > 0)
					{
						GetTextExtentPoint32 ( ps.hdc, (LPCTSTR)buf,iter->m_ComBreak ,&TextSize );
						LPtoDP (ps.hdc,(POINT *)&TextSize,1);
						curx += TextSize.cx;
						SetTextColor (ps.hdc,m_ArgColor);
						TextOut (ps.hdc,curx,cury,(LPCTSTR)&(buf[iter->m_ComBreak]),iter->m_Data.GetLength () - iter->m_ComBreak);
					}
					iter->m_Data.ReleaseBuffer ();
					curx = 0;
				}
				else
				{
				}
				cury += c_FontSize;
				cury += c_LineSpace;
				iter++;
			}

			SetTextColor (ps.hdc,m_CurLineColor);
			TextOut (ps.hdc,curx,cury,(LPCTSTR)m_CurrentLine,m_CurrentLine.GetLength ());
			GetTextExtentPoint32 ( ps.hdc, (LPCTSTR)m_CurrentLine, m_CursorPos ,&TextSize );
			LPtoDP (ps.hdc,(POINT *)&TextSize,1);
			m_CursorLoc.x = TextSize.cx;
			m_CursorLoc.y = cury;

			if (m_ShowCursor && m_CursorFlash)
			{
				temppen = (HPEN) SelectObject (ps.hdc,m_CursorPen);
				MoveToEx (ps.hdc,m_CursorLoc.x,m_CursorLoc.y,NULL);
				LineTo (ps.hdc,m_CursorLoc.x,m_CursorLoc.y+c_FontSize);
				SelectObject (ps.hdc,temppen);
			}

			SelectObject (ps.hdc,oldfont);

			EndPaint (thewnd,&ps);
			return 0;
		}
		break;
	case WM_TIMER:
		if (wParam == c_FlashTimerID)
		{
			m_CursorFlash = !m_CursorFlash;
			{
				RECT temprect;
				temprect.left = m_CursorLoc.x;
				temprect.top = m_CursorLoc.y;
				temprect.right = m_CursorLoc.x+2;
				temprect.bottom = m_CursorLoc.y+c_FontSize;
				InvalidateRect (m_Window,&temprect,TRUE);
			}
		}
		else if (wParam == c_ServerTimerID)
		{
			int num,i;
			char line[256];

			if (m_Attached)
			{
				try
				{
					num = NumOutputLines ();
					for (i=0;i<num;i++)
					{
						GetOutputLine (256,(unsigned char *) line);
						PushLine (line);
					}
				}
				catch (...)
				{
					LineData data ("detach all",LINE_COMMAND);
					ParseLine (data);

					DetachServer (data);
					PushLine ("Error: Had to detach from server");
				}
			}
		}
		break;
	case WM_KEYDOWN:
		switch (wParam)
		{
		case VK_LEFT:
			m_CursorPos = clamp<int> (0,m_CursorPos-1,m_CurrentLine.GetLength ());
			InvalidateRect (m_Window,NULL,TRUE);
			break;
		case VK_RIGHT:
			m_CursorPos = clamp<int> (0,m_CursorPos+1,m_CurrentLine.GetLength ());
			InvalidateRect (m_Window,NULL,TRUE);
			break;
		case VK_HOME:
			m_CursorPos = 0;
			InvalidateRect (m_Window,NULL,TRUE);
			break;
		case VK_END:
			m_CursorPos = m_CurrentLine.GetLength ();
			InvalidateRect (m_Window,NULL,TRUE);
			break;
		case VK_DELETE:
			m_CurrentLine.Delete (m_CursorPos);
			InvalidateRect (m_Window,NULL,TRUE);
			break;
		}
		break;
	case WM_VSCROLL:
		switch (LOWORD (wParam))
		{
		case SB_BOTTOM:
			break;
		case SB_ENDSCROLL:
			break;
		case SB_LINEDOWN:
			break;
		case SB_LINEUP:
			break;
		case SB_PAGEDOWN:
			break;
		case SB_PAGEUP:
			break;
		case SB_THUMBPOSITION:
			break;
		case SB_THUMBTRACK:
			break;
		case SB_TOP:
			break;
		}
		InvalidateRect (m_Window,NULL,FALSE);
		break;
	case WM_CHAR:
		switch (wParam)
		{
		case '\b':
			if (m_CursorPos)
			{
				m_CurrentLine.Delete (m_CursorPos-1);
				m_CursorPos--;
			}
			break;
		case '\r':
			{
				m_CurrentLine.Insert (m_CursorPos,'\0');
				LineData data(m_CurrentLine,LINE_COMMAND);

				ParseLine (data);
				PushLine (data);
				ExecuteLine (data);
				m_CurrentLine = "";
				m_CursorPos = 0;


				LineData *iter;

				iter = &(m_Lines[m_TopDisplay]);
				int count=0;
				int i;
				for (i=m_TopDisplay;i<m_NumLines;i++)
				{
					count++;
					iter++;
				}
				if (count > m_DisplayLines)
				{
					int min,max;
					m_TopDisplay++;
					GetScrollRange (m_Window,SB_VERT,&min,&max);
					SetScrollRange (m_Window,SB_VERT,0,max+1,FALSE);
				}
			}
			break;
		default:
			m_CurrentLine.Insert (m_CursorPos,wParam);
			m_CursorPos++;
			break;
		}
		InvalidateRect (m_Window,NULL,TRUE);
		break;
	}

	return DefWindowProc (thewnd,themsg,wParam,lParam);
}

LRESULT WINAPI NCOMLINE::CommandLineWndProc (HWND thewnd,UINT themsg,WPARAM wParam,LPARAM lParam)
{
	void *temp;

	switch (themsg)
	{
	case WM_CREATE:
		CREATESTRUCT *cs;

		cs = (CREATESTRUCT *) lParam;
		temp = cs->lpCreateParams;
		SetWindowLong (thewnd,GWL_USERDATA,(long) temp);

		break;
	case WM_DESTROY:
		SetWindowLong (thewnd,GWL_USERDATA,0);
		break;
	default:
		CCommandLineClient *fred = (CCommandLineClient *) GetWindowLong (thewnd,GWL_USERDATA);
		if (fred)
			return fred->WndProc (thewnd,themsg,wParam,lParam);
		break;
	}

	return DefWindowProc (thewnd,themsg,wParam,lParam);
}
