/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- December 1997
 */

// wincvsView.cpp : implementation of the CWincvsView class
//

#include "stdafx.h"
#include <atlconv.h>
#include "wincvs.h"
#include "wincvsDoc.h"
#include "wincvsView.h"
#include "wincvs_winutil.h"
#include "MainFrm.h"

#include "Persistent.h"
#include "CvsPrefs.h"
#include "AppGlue.h"
#include "TclGlue.h"
#include "PythonGlue.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// critical section to protect while accessing MFC from cvs
CRITICAL_SECTION CCvsThreadLock::m_lock;

class CCharFormat : public WCHARFORMAT
{
public:
	CCharFormat()
	{
		USES_CONVERSION;
		CString strDefFont = _T("Courier New");
		//VERIFY(strDefFont.LoadString(IDS_DEFFONT));
		cbSize = sizeof(WCHARFORMAT);
		dwMask = CFM_BOLD|CFM_ITALIC|CFM_UNDERLINE|CFM_STRIKEOUT|CFM_SIZE|
			CFM_COLOR|CFM_OFFSET|CFM_PROTECTED;
		dwEffects = CFE_AUTOCOLOR;
		yHeight = 200; //10pt
		yOffset = 0;
		crTextColor = RGB(0, 0, 0);
		bCharSet = 0;
		bPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
		ASSERT(strDefFont.GetLength() < LF_FACESIZE);
		lstrcpynA(szFaceName, T2A((LPTSTR) (LPCTSTR) strDefFont), LF_FACESIZE);
		dwMask |= CFM_FACE;
	}
	CCharFormat(const WCHARFORMAT &newf)
	{
		*(WCHARFORMAT *)this = newf;
	}
	inline operator WCHARFORMAT &()
	{
		return *(WCHARFORMAT *)this;
	}
};

static CPersistentT<CCharFormat> gViewFont("P_ViewFont", CCharFormat(), kNoClass);

/////////////////////////////////////////////////////////////////////////////
// CWincvsView

IMPLEMENT_DYNCREATE(CWincvsView, CRichEditView)

BEGIN_MESSAGE_MAP(CWincvsView, CRichEditView)
	//{{AFX_MSG_MAP(CWincvsView)
	ON_WM_DESTROY()
	ON_WM_SETFOCUS()
	ON_WM_SIZE()
	ON_COMMAND(ID_FORMAT_FONT, OnMyFormatFont)
	ON_WM_KEYDOWN()
	ON_WM_DROPFILES()
	ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll)
	ON_WM_RBUTTONDOWN()
	ON_WM_GETDLGCODE()
	//}}AFX_MSG_MAP
	// Standard printing commands
	ON_COMMAND(ID_FILE_PRINT, CRichEditView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, CRichEditView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CRichEditView::OnFilePrintPreview)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CWincvsView construction/destruction

char *CWincvsView::sbuf = 0L;
int CWincvsView::snumbuf = 0;

CWincvsView::CWincvsView()
{
	fRtfFormat = ::RegisterClipboardFormat(_T("Rich Text Format"));
}

CWincvsView::~CWincvsView()
{
	snumbuf = 0;
	if(sbuf != 0L)
	{
		free(sbuf);
		sbuf = 0L;
	}
	PyInvalUICache();
	CPython_Interp::ClosePythonSupport();
}

BOOL CWincvsView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs

	BOOL bRet = CRichEditView::PreCreateWindow(cs);
	cs.style |= WS_TABSTOP | ES_MULTILINE;
	return  bRet;
}

/////////////////////////////////////////////////////////////////////////////
// CWincvsView drawing

void CWincvsView::OnDraw(CDC* pDC)
{
	//CWincvsDoc* pDoc = GetDocument();
	//ASSERT_VALID(pDoc);

	// TODO: add draw code for native data here
	// TODO: also draw all OLE items in the document

	// Draw the selection at an arbitrary position.  This code should be
	//  removed once your real drawing code is implemented.  This position
	//  corresponds exactly to the rectangle returned by CWincvsCntrItem,
	//  to give the effect of in-place editing.

	// TODO: remove this code when final draw code is complete.

	//if (m_pSelection == NULL)
	//{
	//	POSITION pos = pDoc->GetStartPosition();
	//	m_pSelection = (CWincvsCntrItem*)pDoc->GetNextClientItem(pos);
	//}
	//if (m_pSelection != NULL)
	//	m_pSelection->Draw(pDC, CRect(10, 10, 210, 210));
}

void CWincvsView::OnInitialUpdate()
{
	CRichEditView::OnInitialUpdate();

	CRichEditCtrl& edit = GetRichEditCtrl();
	edit.SetSel(0, -1);
	SetCharFormat((CCharFormat)gViewFont);
	edit.SetSel(-1, -1);
}

/////////////////////////////////////////////////////////////////////////////
// CWincvsView printing

BOOL CWincvsView::OnPreparePrinting(CPrintInfo* pInfo)
{
	return DoPreparePrinting(pInfo);
	//return CRichEditView::OnPreparePrinting(pInfo);
}

void CWincvsView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)
{
	// TODO: add extra initialization before printing
	CRichEditView::OnBeginPrinting(pDC, pInfo);
}

void CWincvsView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)
{
	// TODO: add cleanup after printing
	CRichEditView::OnEndPrinting(pDC, pInfo);
}

void CWincvsView::OnDestroy()
{
	// Deactivate the item on destruction; this is important
	// when a splitter view is being used.
	CRichEditView::OnDestroy();
	//COleClientItem* pActiveItem = GetDocument()->GetInPlaceActiveItem(this);
	//if (pActiveItem != NULL && pActiveItem->GetActiveView() == this)
	//{
	//	pActiveItem->Deactivate();
	//	ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);
	//}
}


/////////////////////////////////////////////////////////////////////////////
// OLE Client support and commands

BOOL CWincvsView::IsSelected(const CObject* pDocItem) const
{
	// The implementation below is adequate if your selection consists of
	//  only CWincvsCntrItem objects.  To handle different selection
	//  mechanisms, the implementation here should be replaced.

	// TODO: implement this function that tests for a selected OLE client item

	//return pDocItem == m_pSelection;
	return CRichEditView::IsSelected(pDocItem);
}

#if 0
// The following command handler provides the standard keyboard
//  user interface to cancel an in-place editing session.  Here,
//  the container (not the server) causes the deactivation.
void CWincvsView::OnCancelEditCntr()
{
	// Close any in-place active item on this view.
	COleClientItem* pActiveItem = GetDocument()->GetInPlaceActiveItem(this);
	if (pActiveItem != NULL)
	{
		pActiveItem->Close();
	}
	ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);
}
#endif

// Special handling of OnSetFocus and OnSize are required for a container
//  when an object is being edited in-place.
void CWincvsView::OnSetFocus(CWnd* pOldWnd)
{
//	COleClientItem* pActiveItem = GetDocument()->GetInPlaceActiveItem(this);
//	if (pActiveItem != NULL &&
//		pActiveItem->GetItemState() == COleClientItem::activeUIState)
//	{
		// need to set focus to this item if it is in the same view
//		CWnd* pWnd = pActiveItem->GetInPlaceWindow();
//		if (pWnd != NULL)
//		{
//			pWnd->SetFocus();   // don't call the base class
//			return;
//		}
//	}

	CRichEditView::OnSetFocus(pOldWnd);
}

void CWincvsView::OnSize(UINT nType, int cx, int cy)
{
	CRichEditView::OnSize(nType, cx, cy);
//	COleClientItem* pActiveItem = GetDocument()->GetInPlaceActiveItem(this);
//	if (pActiveItem != NULL)
//		pActiveItem->SetItemRects();
}

void CWincvsView::FeedThisLine(const char *buf, int numbuf, bool isStderr)
{
	CRichEditCtrl& edit = GetRichEditCtrl();
	edit.SetSel(-1, -1);

	WCHARFORMAT cf;
	cf.cbSize = sizeof(WCHARFORMAT);
	cf = GetCharFormatSelection();

	bool colorChanged = false;
	COLORREF oldColor = cf.crTextColor;
	DWORD oldEffect = cf.dwEffects;
	CString s(buf, numbuf);

	if(s.GetLength() >= 5 && s[1] == ' ' &&
		(s[0] == 'M' || s[0] == 'C' || s[0] == 'U' ||
		s[0] == 'P' || s[0] == 'N' || s[0] == 'A' ||
		s[0] == 'R' || s[0] == 'I' || s[0] == '?' ||
		s[0] == '<' || s[0] == '>'))
	{
		colorChanged = true;
		switch(s[0])
		{
		default:
		case '?' :
		case 'I' :
			cf.crTextColor = RGB(0x80, 0x80, 0x00);
			break;
		case 'U' :
		case 'N' :
			cf.crTextColor = RGB(0x00, 0xA0, 0x00);
			break;
		case 'M' :
		case 'A' :
		case 'R' :
			cf.crTextColor = RGB(0xFF, 0x00, 0xFF);
			break;
		case 'C' :
		case '<' :
			cf.crTextColor = RGB(0xFF, 0x00, 0x00);
			break;
		case '>' :
		case 'P' :
			cf.crTextColor = RGB(0x00, 0x00, 0xFF);
			break;
		}
		cf.dwMask = CFM_COLOR;
		cf.dwEffects = 0;
		SetCharFormat(cf);
	}
	else if(isStderr)
	{
		colorChanged = true;
		cf.crTextColor = RGB(0xFF, 0x80, 0x00);
		cf.dwMask = CFM_COLOR;
		cf.dwEffects = 0;
		SetCharFormat(cf);
	}

	edit.ReplaceSel(s);

	if(colorChanged)
	{
		cf.dwMask = CFM_COLOR;
		cf.crTextColor = oldColor;
		cf.dwEffects = oldEffect;
		SetCharFormat(cf);
	}
}

WCHARFORMAT CWincvsView::GetCurFormat(bool jumpEnd)
{
	CRichEditCtrl& edit = GetRichEditCtrl();
	if(jumpEnd)
		edit.SetSel(-1, -1);

	WCHARFORMAT cf;
	cf.cbSize = sizeof(WCHARFORMAT);
	return GetCharFormatSelection();
}

void CWincvsView::OutColor(const char *txt, long len)
{
	// synchonize it since the thread calls it
	CCvsThreadLock threadLock;

	CString s;
	for(int i = 0; i < len; i++)
	{
		char c = *txt++;
		if(c == 0x0d || c == 0x0a)
		{
			s += (char)0x0d;
			s += (char)0x0a;
			// special case if we got already \r\n
			if((i+1) < len && c == 0x0d && txt[0] == 0x0a)
				i++;
		}
		else
			s += c;
	}

	CRichEditCtrl& edit = GetRichEditCtrl();
	edit.ReplaceSel(s);
}

void CWincvsView::OutColor(kConsoleColor color)
{
	CCvsThreadLock threadLock;

	CRichEditCtrl& edit = GetRichEditCtrl();

	WCHARFORMAT cf = GetCurFormat(false);
	cf.dwEffects = 0;

	switch(color)
	{
		default:
			return;
			break;
		case kBrown :
			cf.dwMask = CFM_COLOR;
			cf.crTextColor = RGB(0x80, 0x80, 0x00);
			break;
		case kGreen :
			cf.dwMask = CFM_COLOR;
			cf.crTextColor = RGB(0x00, 0xA0, 0x00);
			break;
		case kMagenta :
			cf.dwMask = CFM_COLOR;
			cf.crTextColor = RGB(0xFF, 0x00, 0xFF);
			break;
		case kRed :
			cf.dwMask = CFM_COLOR;
			cf.crTextColor = RGB(0xFF, 0x00, 0x00);
			break;
		case kBlue :
			cf.dwMask = CFM_COLOR;
			cf.crTextColor = RGB(0x00, 0x00, 0xFF);
			break;
		case kBold :
			cf.dwMask = CFM_BOLD;
			cf.dwEffects = CFE_BOLD;
			break;
		case kItalic :
			cf.dwMask = CFM_ITALIC;
			cf.dwEffects = CFE_ITALIC;
			break;
		case kUnderline :
			cf.dwMask = CFM_UNDERLINE;
			cf.dwEffects = CFE_UNDERLINE;
			break;
	}

	SetCharFormat(cf);
}

void CWincvsView::OutColor(WCHARFORMAT & format)
{
	CCvsThreadLock threadLock;

	CRichEditCtrl& edit = GetRichEditCtrl();
	SetCharFormat(format);
}

void CWincvsView::OutConsole(const char *txt, long len, bool isStderr)
{
	// synchonize it since the thread calls it
	CCvsThreadLock threadLock;

	if(len == 0)
		return;

	if(sbuf == 0L)
	{
		sbuf = (char *)malloc((MAX_CHAR_BY_LINE + 3) * sizeof(char));
		if(sbuf == 0L)
			return;
	}

	int i = 0;
	char c;
once_again:
	for(; i < len && snumbuf < MAX_CHAR_BY_LINE; snumbuf++)
	{
		c = txt[i++];
		if(c == 0x0d || c == 0x0a)
			break;
		sbuf[snumbuf] = c;
	}

	// special case if we got already \r\n
	if(i < len && c == 0x0d && txt[i] == 0x0a)
		i++;

	if(c == 0x0d || c == 0x0a || snumbuf == MAX_CHAR_BY_LINE)
	{
		sbuf[snumbuf++] = 0x0d;
		sbuf[snumbuf++] = 0x0a;
		sbuf[snumbuf++] = 0x00;
	
		FeedThisLine(sbuf, snumbuf, isStderr);
		snumbuf = 0;
	}

	if(i != len)
		goto once_again;
}

/////////////////////////////////////////////////////////////////////////////
// CWincvsView diagnostics

#ifdef _DEBUG
void CWincvsView::AssertValid() const
{
	CRichEditView::AssertValid();
}

void CWincvsView::Dump(CDumpContext& dc) const
{
	CRichEditView::Dump(dc);
}

CWincvsDoc* CWincvsView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWincvsDoc)));
	return (CWincvsDoc*)m_pDocument;
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CWincvsView message handlers

void CWincvsView::OnMyFormatFont() 
{
	CRichEditCtrl& edit = GetRichEditCtrl();
	edit.SetSel(0, -1);
	CRichEditView::OnFormatFont();
	edit.SetSel(-1, -1);
	gViewFont = CCharFormat(GetCharFormatSelection());
	gCvsPrefs.save();
}

void CWincvsView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CRichEditView::OnKeyDown(nChar, nRepCnt, nFlags);

	if(nChar != VK_RETURN)
	{
		return;
	}

	long len = GetTextLength();
	if(len == 0)
		return;

	TCHAR *buf = (char *)malloc((len + 1) * sizeof(TCHAR));
	if(buf == 0L)
		return;

	TEXTRANGE textRange;
	textRange.chrg.cpMin = 0;
	textRange.chrg.cpMax = len;
	textRange.lpstrText = buf;
	GetRichEditCtrl().SendMessage(EM_GETTEXTRANGE, 0, (LPARAM)&textRange);

	long nStartChar, nEndChar;
	GetRichEditCtrl().GetSel(nStartChar, nEndChar);

	if (nStartChar == nEndChar) // i.e. there is no selection
	{
		// get the end and the beginning of the line
		if(nStartChar > 0 && buf[nStartChar] == '\r')
			nStartChar--;
		while(nStartChar > 0 && buf[nStartChar] != '\n' && buf[nStartChar] != '\r')
			nStartChar--;
		if(buf[nStartChar] == '\n' || buf[nStartChar] == '\r')
			nStartChar++;
		while(nEndChar < len && buf[nEndChar] != '\n' && buf[nEndChar] != '\r')
			nEndChar++;
	}

	long length = nEndChar - nStartChar;
	if(length > 0 && nStartChar < len)
	{
		OutConsole("\n", 1);
		buf[nEndChar] = '\0';
		if(gCvsPrefs.GetUseShell() == SHELL_PYTHON && CPython_Interp::IsAvail())
		{
			CPython_Interp interp;
			interp.DoScript(buf + nStartChar);
		}
		else if(gCvsPrefs.GetUseShell() == SHELL_TCL && CTcl_Interp::IsAvail())
		{
			CTcl_Interp interp;
			interp.DoScript(buf + nStartChar);
		}
	}

	free(buf);
}

HRESULT CWincvsView::QueryAcceptData(LPDATAOBJECT lpdataobj,
	CLIPFORMAT* lpcfFormat, DWORD dwReco, BOOL bReally, HGLOBAL hMetaPict)
{
	ASSERT(lpcfFormat != NULL);
	if (!bReally) // not actually pasting
		return S_OK;
	// if direct pasting a particular native format allow it
	if (IsRichEditFormat(*lpcfFormat))
		return S_OK;

	COleDataObject dataobj;
	dataobj.Attach(lpdataobj, FALSE);
	// if format is 0, then force particular formats if available
	if (*lpcfFormat == 0 && (m_nPasteType == 0))
	{
		if (fRtfFormat != 0 && dataobj.IsDataAvailable(fRtfFormat)) // native avail, let richedit do as it wants
		{
			*lpcfFormat = fRtfFormat;
			return S_OK;
		}
		else if (dataobj.IsDataAvailable(CF_TEXT))
		{
			*lpcfFormat = CF_TEXT;
			return S_OK;
		}
	}
	// *DO NOT* paste OLE formats
	// crash reported by Gerhard Möller
	//DoPaste(dataobj, *lpcfFormat, hMetaPict);
	return S_FALSE;
}

BOOL CWincvsView::CanPaste() const
{
	return (CountClipboardFormats() != 0) &&
		(IsClipboardFormatAvailable(CF_TEXT) ||
		IsClipboardFormatAvailable(fRtfFormat) ||
		//IsClipboardFormatAvailable(_oleData.cfEmbedSource) ||
		//IsClipboardFormatAvailable(_oleData.cfEmbeddedObject) ||
		//IsClipboardFormatAvailable(_oleData.cfFileName) ||
		//IsClipboardFormatAvailable(_oleData.cfFileNameW) ||
		//IsClipboardFormatAvailable(CF_METAFILEPICT) ||
		//IsClipboardFormatAvailable(CF_DIB) ||
		//IsClipboardFormatAvailable(CF_BITMAP) ||
		GetRichEditCtrl().CanPaste());
}

void CWincvsView::OnDropFiles(HDROP hDropInfo) 
{	
	CWnd* pWnd = AfxGetMainWnd();	//look in InitInstance m_pMainWnd = m_mainFrame;

	if( pWnd )
	{
		CMainFrame* pMainFrame = (CMainFrame*)pWnd;
		
		if( pMainFrame->GetSafeHwnd() != NULL )
		{
			pMainFrame->DropFiles( hDropInfo );
		}
	}

	//this cause the crash, remove
	//CRichEditView::OnDropFiles(hDropInfo);
}

void CWincvsView::OnEditClearAll() 
{
	CRichEditCtrl& edit = GetRichEditCtrl();
	edit.SetSel(0, -1);
	edit.ReplaceSel("", TRUE);
}

void CWincvsView::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CWincvsApp* pApp=static_cast<CWincvsApp*>(AfxGetApp());
	CMenu* pSubMenu=::GetSubCMenu(ID_EDIT_UNDO,this);
	if (pSubMenu) {
		ClientToScreen(&point);
		pSubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this);
	}
//	CRichEditView::OnRButtonDown(nFlags, point);
}

UINT CWincvsView::OnGetDlgCode() 
{
	LRESULT DlgCode;
	DlgCode = CRichEditView::OnGetDlgCode();
	if (0x8000 & GetKeyState(VK_TAB))
		return  DlgCode & ~(DLGC_WANTTAB | DLGC_WANTALLKEYS | DLGC_WANTCHARS);
	else
		return DlgCode;
}
