/** 
 * @file  MergeDocLineDiffs.cpp
 *
 * @brief Implementation file for word diff highlighting (F4) for merge edit & detail views
 *
 */
// RCS ID line follows -- this is updated by CVS
// $Id: MergeDocLineDiffs.cpp 4983 2008-01-31 21:08:06Z jtuc $

#include "stdafx.h"
#include "Merge.h"
#include "MainFrm.h"

#include "MergeEditView.h"
#include "MergeDiffDetailView.h"
#include "stringdiffs.h"

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


/**
 * @brief Display the line/word difference highlight in edit view
 */
static void
HighlightDiffRect(CMergeEditView * pView, const CRect & rc)
{
	if (rc.top == -1)
	{
		// Should we remove existing selection ?
	}
	else
	{
		// select the area
		// with anchor at left and caret at right
		// this seems to be conventional behavior in Windows editors
		pView->SelectArea(rc.TopLeft(), rc.BottomRight());
		pView->SetCursorPos(rc.BottomRight());
		pView->SetNewAnchor(rc.TopLeft());
		// try to ensure that selected area is visible
		pView->EnsureVisible(rc.TopLeft(), rc.BottomRight());
	}
}

/**
 * @brief Display the line/word difference highlight in detail view
 *
 * Not combined with HighlightDiffRect(CMergeEditView *.. because
 * CMergeEditView & CMergeDiffDetailView do not inherit from a common base
 * which implements SelectArea etc.
 */
static void
HighlightDiffRect(CMergeDiffDetailView * pView, const CRect & rc)
{
	if (rc.top == -1)
	{
		// Should we remove existing selection ?
	}
	else
	{
		// select the area
		// with anchor at left and caret at right
		// this seems to be conventional behavior in Windows editors
		pView->SelectArea(rc.TopLeft(), rc.BottomRight());
		pView->SetCursorPos(rc.BottomRight());
		pView->SetNewAnchor(rc.TopLeft());
		// try to ensure that selected area is visible
		pView->EnsureVisible(rc.TopLeft());
	}
}

/**
 * @brief Highlight difference in current line (left & right panes)
 */
void CMergeDoc::Showlinediff(CMergeEditView * pView, DIFFLEVEL difflvl)
{
	CRect rc[3];
	int pane;
	CCrystalTextView *pCrystalTextView[3] = {m_pView[0], m_pView[1], m_pView[2]};

	Computelinediff(pCrystalTextView, pView->GetCursorPos().y, rc, difflvl);

	IF_IS_TRUE_ALL ((rc[pane].top == -1), pane, m_nBuffers)
	{
		String caption = theApp.LoadString(IDS_LINEDIFF_NODIFF_CAPTION);
		String msg = theApp.LoadString(IDS_LINEDIFF_NODIFF);
		MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK);
		return;
	}

	// Actually display selection areas on screen in both edit panels
	for (pane = 0; pane < m_nBuffers; pane++)
		HighlightDiffRect(m_pView[pane], rc[pane]);
}

/**
 * @brief Highlight difference in diff bar's current line (top & bottom panes)
 */
void CMergeDoc::Showlinediff(CMergeDiffDetailView * pView, DIFFLEVEL difflvl)
{
	CRect rc[3];
	int pane;
	CCrystalTextView *pCrystalTextView[3] = {m_pView[0], m_pView[1], m_pView[2]};

	Computelinediff(pCrystalTextView, pView->GetCursorPos().y, rc, difflvl);

	IF_IS_TRUE_ALL ((rc[pane].top == -1), pane, m_nBuffers)
	{
		String caption = theApp.LoadString(IDS_LINEDIFF_NODIFF_CAPTION);
		String msg = theApp.LoadString(IDS_LINEDIFF_NODIFF);
		MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK);
		return;
	}

	// Actually display selection areas on screen in both detail panels
	for (pane = 0; pane < m_nBuffers; pane++)
		HighlightDiffRect(m_pDetailView[pane], rc[pane]);
}

/**
 * @brief Ensure that i1 is no greater than w1
 */
static void
Limit(int & i1, int w1)
{
	if (i1>=w1)
		i1 = w1;
}

/**
 * @brief Set highlight rectangle for a given difference (begin->end in line)
 */
static void
SetLineHighlightRect(int begin, int end, int line, int width, CRect * rc)
{
	if (begin == -1)
	{
		begin = end = 0;
	}
	else
	{
		++end; // MergeDoc needs to point past end
	}
	// Chop off any reference to eol characters
	// TODO: Are we dropping the last non-eol character,
	// because MergeDoc points past the end ?
	Limit(begin, width);
	Limit(end, width);
	CPoint ptBegin(begin,line), ptEnd(end,line);
	*rc = CRect(ptBegin, ptEnd);
}

/**
 * @brief Construct the highlight rectangles for diff # whichdiff
 */
static void
ComputeHighlightRects(int nFiles, const wdiffarray & worddiffs, int whichdiff, int line, int width[3], CRect rc[3])
{
	ASSERT(whichdiff >= 0 && whichdiff < worddiffs.GetSize());
	const wdiff & diff = worddiffs[whichdiff];

	for (int file = 0; file < nFiles; file++)
		SetLineHighlightRect(diff.begin[file], diff.end[file], line, width[file], &rc[file]);
	
}

/**
 * @brief Returns rectangles to highlight in both views (to show differences in line specified)
 */
void CMergeDoc::Computelinediff(CCrystalTextView * pView[], int line, CRect rc[], DIFFLEVEL difflvl)
{
	// Local statics are used so we can cycle through diffs in one line
	// We store previous state, both to find next state, and to verify
	// that nothing has changed (else, we reset the cycle)
	static CCrystalTextView * lastView = 0;
	static int lastLine = -1;
	static CRect lastRc[3];
	static int whichdiff=-2; // last diff highlighted (-2==none, -1=whole line)
	int file;

	// Only remember place in cycle if same line and same view
	if (lastView != pView[0] || lastLine != line)
	{
		lastView = pView[0];
		lastLine = line;
		whichdiff = -2; // reset to not in cycle
	}

	DIFFOPTIONS diffOptions = {0};
	m_diffWrapper.GetOptions(&diffOptions);

	CString str[3];	
	for (file = 0; file < m_nBuffers; file++)
		str[file] = CString(pView[file]->GetLineChars(line), pView[file]->GetFullLineLength(line));

	if (diffOptions.bIgnoreEol)
	{
		/* Commented out code because GetLineActualLength is buggy
		// Chop of eol (end of line) characters
		int len1 = pView[0]->GetLineActualLength(line);
		str1 = str1.Left(len1);
		int len2 = pView[1]->GetLineActualLength(line);
		str2 = str2.Left(len2);
		*/
		for (file = 0; file < m_nBuffers; file++)
		{
			int i = str[file].GetLength()-1;
			while (i>=0 && (str[file][i]=='\r' || str[file][i]=='\n'))
				--i;
			if (i+1 < str[file].GetLength())
				str[file] = str[file].Left(i+1);
		}
	}

	// We truncate diffs to remain inside line (ie, to not flag eol characters)
	int width[3];
	for (file = 0; file < m_nBuffers; file++)
		width[file] = pView[file]->GetLineLength(line);

	// Options that affect comparison
	bool casitive = !diffOptions.bIgnoreCase;
	int xwhite = diffOptions.nIgnoreWhitespace;

	// Make the call to stringdiffs, which does all the hard & tedious computations
	wdiffarray worddiffs;
	bool breakType = GetBreakType();
	sd_ComputeWordDiffs(m_nBuffers, str, casitive, xwhite, breakType, difflvl == BYTEDIFF, &worddiffs);

	if (!worddiffs.GetSize())
	{
		// signal to caller that there was no diff
		for (file = 0; file < m_nBuffers; file++)
			rc[file].top = -1;
		
		return;
	}

	// Are we continuing a cycle from the same place ?
	if (whichdiff >= worddiffs.GetSize())
		whichdiff = -2; // Clearly not continuing the same cycle, reset to not in cycle
	
	// After last diff, reset to get full line again
	if (whichdiff == worddiffs.GetUpperBound())
		whichdiff = -2;

	// Check if current line has changed enough to reset cycle
	if (whichdiff >= 0)
	{
		// Recompute previous highlight rectangle
		CRect rcx[3];
		ComputeHighlightRects(m_nBuffers, worddiffs, whichdiff, line, width, rcx);
		IF_IS_TRUE_ALL (rcx[file] == lastRc[file], file, m_nBuffers) {}
		else
		{
			// Something has changed, reset cycle
			whichdiff = -2;
		}
	}

	int begin[3] = {-1, -1, -1}, end[3] = {-1, -1, -1};

	if (whichdiff == -2)
	{
		// Find starting locations for both sides
		// Have to look for first valid starting location for each side
		int i;
		for (i=0; i<worddiffs.GetSize(); ++i)
		{
			const wdiff & diff = worddiffs[i];
			for (file = 0; file < m_nBuffers; file++)
			{
				if (begin[file] == -1 && diff.begin[file] != -1)
					begin[file] = diff.begin[file];
			}
			IF_IS_TRUE_ALL (begin[file] != -1, file, m_nBuffers)
			{
				break; // found both
			}
		}
		// Find ending locations for both sides
		// Have to look for last valid starting location for each side
		for (i=worddiffs.GetUpperBound(); i>=0; --i)
		{
			const wdiff & diff = worddiffs[i];
			for (file = 0; file < m_nBuffers; file++)
			{
				if (end[file] == -1 && diff.end[file] != -1)
					end[file] = diff.end[file];
			}
			IF_IS_TRUE_ALL (end[file] != -1, file, m_nBuffers)
			{
				break; // found both
			}
		}
		for (file = 0; file < m_nBuffers; file++)
			SetLineHighlightRect(begin[file], end[file], line, width[file], &rc[file]);
		whichdiff = -1;
	}
	else
	{
		// Advance to next diff (and earlier we checked for running off the end)
		++whichdiff;
		ASSERT(whichdiff < worddiffs.GetSize());

		// highlight one particular diff
		ComputeHighlightRects(m_nBuffers, worddiffs, whichdiff, line, width, rc);
		for (file = 0; file < m_nBuffers; file++)
			lastRc[file] = rc[file];
	}
}

/**
 * @brief Return array of differences in specified line
 * This is used by algorithm for line diff coloring
 * (Line diff coloring is distinct from the selection highlight code)
 */
void CMergeDoc::GetWordDiffArray(int nLineIndex, wdiffarray *pworddiffs)
{
	int file;
	for (file = 0; file < m_nBuffers; file++)
	{
		if (nLineIndex >= m_pView[file]->GetLineCount()) return;
	}

	DIFFOPTIONS diffOptions = {0};
	m_diffWrapper.GetOptions(&diffOptions);

	CString str[3];
	for (file = 0; file < m_nBuffers; file++)
		str[file] = CString(m_pView[file]->GetLineChars(nLineIndex), m_pView[file]->GetFullLineLength(nLineIndex));

	if (diffOptions.bIgnoreEol)
	{
		/* Commented out code because GetLineActualLength is buggy
		// Chop of eol (end of line) characters
		int len1 = pView[0]->GetLineActualLength(line);
		str1 = str1.Left(len1);
		int len2 = pView[1]->GetLineActualLength(line);
		str2 = str2.Left(len2);
		*/
		for (file = 0; file < m_nBuffers; file++)
		{
			int i = str[file].GetLength()-1;
			while (i>=0 && (str[file][i]=='\r' || str[file][i]=='\n'))
				--i;
			if (i+1 < str[file].GetLength())
				str[file] = str[file].Left(i+1);
		}
	}

	// Options that affect comparison
	bool casitive = !diffOptions.bIgnoreCase;
	int xwhite = diffOptions.nIgnoreWhitespace;
	int breakType = GetBreakType(); // whitespace only or include punctuation
	bool byteColoring = GetByteColoringOption();

		// Make the call to stringdiffs, which does all the hard & tedious computations
	sd_ComputeWordDiffs(m_nBuffers, str, casitive, xwhite, breakType, byteColoring, pworddiffs);

	return;
}

