/////////////////////////////////////////////////////////////////////////////
//    WinMerge:  an interactive diff/merge utility
//    Copyright (C) 1997-2000  Thingamahoochie Software
//    Author: Dean Grimm
//
//    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 2 of the License, 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.
//
/////////////////////////////////////////////////////////////////////////////
/** 
 * @file  MergeDoc.cpp
 *
 * @brief Implementation file for CMergeDoc
 *
 */
// ID line follows -- this is updated by SVN
// $Id: MergeDoc.cpp 5454 2008-06-09 22:13:20Z kimmov $

#include "stdafx.h"
#include <Shlwapi.h>		// PathCompactPathEx()
#include "UnicodeString.h"
#include "FileTextEncoding.h"
#include "Merge.h"
#include "MainFrm.h"
#include "Environment.h"
#include "Ucs2Utf8.h"
#include "diffcontext.h"	// FILE_SAME
#include "MovedLines.h"
#include "getopt.h"
#include "fnmatch.h"
#include "coretools.h"
#include "MergeEditView.h"
#include "MergeDiffDetailView.h"
#include "cs2cs.h"
#include "childFrm.h"
#include "dirdoc.h"
#include "files.h"
#include "WaitStatusCursor.h"
#include "FileTransform.h"
#include "unicoder.h"
#include "UniFile.h"
#include "locality.h"
#include "OptionsDef.h"
#include "DiffFileInfo.h"
#include "SaveClosingDlg.h"
#include "DiffList.h"
#include "dllver.h"
#include "codepage.h"
#include "paths.h"
#include "OptionsMgr.h"
#include "ProjectFile.h"
#include "MergeLineFlags.h"
#include "FileOrFolderSelect.h"
#include "LineFiltersList.h"
#include "TempFile.h"
#include "codepage_detect.h"

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

/** @brief Max len of path in caption. */
static const UINT CAPTION_PATH_MAX = 50;

int CMergeDoc::m_nBuffersTemp = 2;

/** @brief EOL types */
static LPCTSTR crlfs[] =
{
	_T ("\x0d\x0a"), //  DOS/Windows style
	_T ("\x0a"),     //  UNIX style
	_T ("\x0d")      //  Macintosh style
};

static CString GetLineByteTimeReport(UINT lines, __int64 bytes,
	const COleDateTime & start);

/////////////////////////////////////////////////////////////////////////////
// CMergeDoc

IMPLEMENT_DYNCREATE(CMergeDoc, CDocument)

BEGIN_MESSAGE_MAP(CMergeDoc, CDocument)
	//{{AFX_MSG_MAP(CMergeDoc)
	ON_COMMAND(ID_FILE_SAVE, OnFileSave)
	ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
	ON_COMMAND(ID_FILE_SAVE_MIDDLE, OnFileSaveMiddle)
	ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
	ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
	ON_UPDATE_COMMAND_UI(ID_FILE_SAVEAS_MIDDLE, OnUpdateFileSaveAsMiddle)
	ON_COMMAND(ID_FILE_SAVEAS_MIDDLE, OnFileSaveAsMiddle)
	ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
	ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
	ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
	ON_COMMAND(ID_RESCAN, OnFileReload)
	ON_UPDATE_COMMAND_UI(ID_RESCAN, OnUpdateFileReload)
	ON_COMMAND(ID_FILE_ENCODING, OnFileEncoding)
	ON_UPDATE_COMMAND_UI(ID_FILE_ENCODING, OnUpdateFileEncoding)
	ON_COMMAND_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_9, OnDiffContext)
	ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_9, OnUpdateDiffContext)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CMergeDoc construction/destruction


#pragma warning(disable:4355)

/**
 * @brief Constructor.
 */
CMergeDoc::CMergeDoc()
: m_bEnableRescan(TRUE)
, m_nCurDiff(-1)
, m_pDirDoc(NULL)
, m_bMixedEol(false)
{
	DIFFOPTIONS options = {0};

	m_nBuffers = m_nBuffersTemp;
	m_filePaths.SetSize(m_nBuffers);

	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		m_ptBuf[nBuffer] = new CDiffTextBuffer(this,nBuffer);
		m_pSaveFileInfo[nBuffer] = new DiffFileInfo();
		m_pRescanFileInfo[nBuffer] = new DiffFileInfo();
		m_pView[nBuffer] = NULL;
		m_pDetailView[nBuffer] = NULL;
		m_nBufferType[nBuffer] = BUFFER_NORMAL;
		m_bEditAfterRescan[nBuffer] = FALSE;
	}

	m_nCurDiff=-1;
	m_bEnableRescan = TRUE;
	// COleDateTime m_LastRescan
	curUndo = undoTgt.begin();
	m_pInfoUnpacker = new PackingInfo;
	m_bMergingMode = GetOptionsMgr()->GetBool(OPT_MERGE_MODE);
	m_nDiffContext = GetOptionsMgr()->GetInt(OPT_DIFF_CONTEXT);

	m_diffWrapper.SetDetectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
	options.nIgnoreWhitespace = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
	options.bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
	options.bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
	options.bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
	options.bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);

	m_diffWrapper.SetOptions(&options);
	m_diffWrapper.SetPrediffer(NULL);
}

#pragma warning(default:4355)

/**
 * @brief Destructor.
 *
 * Informs associated dirdoc that mergedoc is closing.
 */
CMergeDoc::~CMergeDoc()
{	
	if (m_pDirDoc)
	{
		m_pDirDoc->MergeDocClosing(this);
		m_pDirDoc = NULL;
	}

	delete m_pInfoUnpacker;
	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		delete m_pSaveFileInfo[nBuffer];
		delete m_pRescanFileInfo[nBuffer];
		delete m_ptBuf[nBuffer];
	}
}

/**
 * @brief Deleted data associated with doc before closing.
 */
void CMergeDoc::DeleteContents ()
{
	CDocument::DeleteContents ();
	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		m_ptBuf[nBuffer]->FreeAll ();
	m_tempFiles[0].Delete();
	m_tempFiles[1].Delete();
}

void CMergeDoc::OnFileEvent (WPARAM /*wEvent*/, LPCTSTR /*pszPathName*/)
{
	/*if (!(theApp.m_dwFlags & EP_NOTIFY_CHANGES))
    return;
	MessageBeep (MB_ICONEXCLAMATION);
	CFrameWnd *pwndMain= (CFrameWnd*) theApp.GetMainWnd ();
	ASSERT (pwndMain);
	if (!pwndMain->IsWindowVisible ())
          ((CMainFrame*) pwndMain)->FlashUntilFocus ();
	if (wEvent & FE_MODIFIED)
  	{
  	  bool bReload = (theApp.m_dwFlags & EP_AUTO_RELOAD) != 0;
  	  if (!bReload)
  	    {
          CString sMsg;
          sMsg.Format (IDS_FILE_CHANGED, pszPathName);
  	      bReload = AfxMessageBox (sMsg, MB_YESNO|MB_ICONQUESTION) == IDYES;
  	    }
  	  if (bReload)
        {
	        POSITION pos = GetFirstViewPosition ();
          ASSERT (pos);
	        CEditPadView *pView;
          do
            {
	            pView = (CEditPadView*) GetNextView (pos);
              pView->PushCursor ();
            }
          while (pos);
          m_xTextBuffer.FreeAll ();
          m_xTextBuffer.LoadFromFile (pszPathName);
	        pos = GetFirstViewPosition ();
          ASSERT (pos);
          do
            {
	            pView = (CEditPadView*) GetNextView (pos);
              pView->PopCursor ();
              HWND hWnd = pView->GetSafeHwnd ();
              ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE|RDW_INTERNALPAINT|RDW_ERASE|RDW_ERASENOW|RDW_UPDATENOW|RDW_NOFRAME);
            }
          while (pos);
        }
    }
  else if (wEvent & FE_DELETED)
    {
      if (!(theApp.m_dwFlags & EP_AUTO_RELOAD))
        {
          CString sMsg;
          sMsg.Format (IDS_FILE_DELETED, pszPathName);
        	AfxMessageBox (sMsg, MB_OK|MB_ICONINFORMATION);
        }
    }*/
}

/**
 * @brief Called when new document is created.
 *
 * Initialises buffers.
 */
BOOL CMergeDoc::OnNewDocument()
{
	if (!CDocument::OnNewDocument())
		return FALSE;

	SetTitle(theApp.LoadString(IDS_FILE_COMPARISON_TITLE).c_str());
	
	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		m_ptBuf[nBuffer]->InitNew ();
	return TRUE;
}

/**
 * @brief Determines currently active view.
 * @return one of MERGEVIEW_INDEX_TYPE values or -1 in error.
 * @todo Detect location pane and return != 1 for it.
 */
int CMergeDoc::GetActiveMergeViewIndexType() const
{
	CMergeDoc * pThis = const_cast<CMergeDoc *>(this);
	// Get active view pointer
	CView * pActiveView = pThis->GetParentFrame()->GetActiveView();
	// Cast it to common base of all our views
	CCrystalTextView* curView = dynamic_cast<CCrystalTextView*> (pActiveView);
	// Now test it against all our views to see which it is
	if (curView == GetView(0))
		return MERGEVIEW_PANE0;
	else if (curView == GetView(1))
		return MERGEVIEW_PANE1;
	else if (m_nBuffers == 3 && curView == GetView(2))
		return MERGEVIEW_PANE2;
	else if (curView == GetDetailView(0))
		return MERGEVIEW_PANE0_DETAIL;
	else if (curView == GetDetailView(1))
		return MERGEVIEW_PANE1_DETAIL;
	else if (m_nBuffers == 3 && curView == GetDetailView(2))
		return MERGEVIEW_PANE2_DETAIL;

	// This assert fired when location pane caused refresh.
	// We can't detect location pane activity, so disable the assert.
	//_RPTF0(_CRT_ERROR, "Invalid view pointer!");
	return -1;
}

/**
 * @brief Return active merge edit view (or left one if neither active)
 */
CMergeEditView * CMergeDoc::GetActiveMergeView()
{
	CView * pActiveView = GetParentFrame()->GetActiveView();
	CMergeEditView * pMergeEditView = dynamic_cast<CMergeEditView *>(pActiveView);
	if (!pMergeEditView)
		pMergeEditView = GetView(0); // default to left view (in case some location or detail view active)
	return pMergeEditView;
}

void CMergeDoc::SetUnpacker(PackingInfo * infoNewHandler)
{
	if (infoNewHandler)
	{
		*m_pInfoUnpacker = *infoNewHandler;
	}
}

void CMergeDoc::SetPrediffer(PrediffingInfo * infoPrediffer)
{
	m_diffWrapper.SetPrediffer(infoPrediffer);
}
void CMergeDoc::GetPrediffer(PrediffingInfo * infoPrediffer)
{
	m_diffWrapper.GetPrediffer(infoPrediffer);
}

/////////////////////////////////////////////////////////////////////////////
// CMergeDoc serialization

void CMergeDoc::Serialize(CArchive& ar)
{
	ASSERT(0); // we do not use CDocument serialization
}

/////////////////////////////////////////////////////////////////////////////
// CMergeDoc commands

/**
 * @brief Save an editor text buffer to a file for prediffing (make UCS-2LE if appropriate)
 *
 * @note 
 * original file is Ansi : 
 *   buffer  -> save as Ansi -> Ansi plugins -> diffutils
 * original file is Unicode (UCS2-LE, UCS2-BE, UTF-8) :
 *   buffer  -> save as UCS2-LE -> Unicode plugins -> convert to UTF-8 -> diffutils
 * (the plugins are optional, not the conversion)
 * @todo Show SaveToFile() errors?
 */
static void SaveBuffForDiff(CMergeDoc::CDiffTextBuffer & buf, LPCTSTR filepath, BOOL bForceUTF8)
{
	ASSERT(buf.m_nSourceEncoding == buf.m_nDefaultEncoding);  
	int orig_codepage = buf.getCodepage();
	int orig_unicoding = buf.getUnicoding();	
	bool orig_bHasBOM = buf.getHasBom();

	// If file was in Unicode
	if (orig_unicoding!=ucr::NONE)
	{
	// we subvert the buffer's memory of the original file encoding
		buf.setUnicoding(ucr::UCS2LE);  // write as UCS-2LE (for preprocessing)
		buf.setCodepage(1200); // should not matter
		buf.setHasBom(true);
	}

	// and we don't repack the file
	PackingInfo * tempPacker = NULL;

	// write buffer out to temporary file
	CString sError;
	int retVal = buf.SaveToFile(filepath, TRUE, sError, tempPacker,
		CRLF_STYLE_AUTOMATIC, FALSE, bForceUTF8);

	// restore memory of encoding of original file
	buf.setUnicoding(orig_unicoding);
	buf.setCodepage(orig_codepage);
	buf.setHasBom(orig_bHasBOM);
}

/**
 * @brief Save files to temp files & compare again.
 *
 * @param bBinary [in,out] [in] If TRUE, compare two binary files
 * [out] If TRUE binary file was detected.
 * @param bIdentical [out] If TRUE files were identical
 * @param bForced [in] If TRUE, suppressing is ignored and rescan
 * is done always
 * @return Tells if rescan was successfully, was suppressed, or
 * error happened
 * If this code is OK, Rescan has detached the views temporarily
 * (positions of cursors have been lost)
 * @note Rescan() ALWAYS compares temp files. Actual user files are not
 * touched by Rescan().
 * @sa CDiffWrapper::RunFileDiff()
 */
int CMergeDoc::Rescan(BOOL &bBinary, IDENTLEVEL &identical,
		BOOL bForced /* =FALSE */)
{
	DIFFOPTIONS diffOptions = {0};
	BOOL diffSuccess;
	int nResult = RESCAN_OK;
	BOOL bFileChanged[3] = {FALSE};
	int nBuffer;

	if (!bForced)
	{
		if (!m_bEnableRescan)
			return RESCAN_SUPPRESSED;
	}

	if (GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED))
	{
		String regexp = GetMainFrame()->m_pLineFilters->GetAsString();
		m_diffWrapper.SetFilterList(regexp.c_str());
	}
	else
	{
		m_diffWrapper.SetFilterList(_T(""));
	}

	m_LastRescan = COleDateTime::GetCurrentTime();

	LPCTSTR tnames[] = {_T("t0_wmdoc"), _T("t1_wmdoc"), _T("t2_wmdoc")};
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		String temp = m_tempFiles[nBuffer].GetPath();
		if (temp.empty())
		{
			temp = m_tempFiles[nBuffer].CreateFromFile(m_filePaths.GetPath(nBuffer).c_str(),
				tnames[nBuffer]);
		}
		if (temp.empty())
			return RESCAN_TEMP_ERR;
	}

	BOOL bForceUTF8 = FALSE;
	String tempPath = env_GetTempPath(NULL);
	IF_IS_TRUE_ALL (m_ptBuf[0]->getCodepage() == m_ptBuf[nBuffer]->getCodepage(), nBuffer, m_nBuffers) {}
	else
		bForceUTF8 = TRUE;
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		m_ptBuf[nBuffer]->SetTempPath(tempPath);
		SaveBuffForDiff(*m_ptBuf[nBuffer], m_tempFiles[nBuffer].GetPath().c_str(), bForceUTF8);
	}

	// Set up DiffWrapper
	m_diffWrapper.SetCreateDiffList(&m_diffList);
	m_diffWrapper.GetOptions(&diffOptions);
	
	// Clear diff list
	m_diffList.Clear();
	m_nCurDiff = -1;
	// Clear moved lines lists
	if (m_diffWrapper.GetDetectMovedBlocks())
	{
		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
			m_diffWrapper.GetMovedLines(nBuffer)->Clear();
	}

	// Set paths for diffing and run diff
	if (m_nBuffers < 3)
		m_diffWrapper.SetPaths(PathContext(m_tempFiles[0].GetPath().c_str(), m_tempFiles[1].GetPath().c_str()), TRUE);
	else
		m_diffWrapper.SetPaths(PathContext(m_tempFiles[0].GetPath().c_str(), m_tempFiles[1].GetPath().c_str(), m_tempFiles[2].GetPath().c_str()), TRUE);
	m_diffWrapper.SetCompareFiles(m_filePaths);
	m_diffWrapper.SetCodepage(bForceUTF8 ? CP_UTF8 : (m_ptBuf[0]->m_encoding.m_unicoding ? CP_UTF8 : m_ptBuf[0]->m_encoding.m_codepage));
	m_diffWrapper.SetCodepage(m_ptBuf[0]->m_encoding.m_unicoding ?
			CP_UTF8 : m_ptBuf[0]->m_encoding.m_codepage);
	diffSuccess = m_diffWrapper.RunFileDiff();

	// Read diff-status
	DIFFSTATUS status;
	m_diffWrapper.GetDiffStatus(&status);
	if (bBinary) // believe caller if we were told these are binaries
		status.bBinaries = TRUE;

	// If comparing whitespaces and
	// other file has EOL before EOF and other not...
	if (!diffOptions.nIgnoreWhitespace && !diffOptions.bIgnoreBlankLines)
	{
		IF_IS_TRUE_ALL ((status.bMissingNL[0] == status.bMissingNL[nBuffer]), nBuffer, m_nBuffers) {}
		else
		{
			// ..lasf DIFFRANGE of file which has EOL must be
			// fixed to contain last line too
			int lineCount[3];
			for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
				lineCount[nBuffer] = m_ptBuf[nBuffer]->GetLineCount();
			m_diffWrapper.FixLastDiffRange(m_nBuffers, lineCount, status.bMissingNL);
		}
	}

	// set identical/diff result as recorded by diffutils
	identical = status.Identical;

	// Determine errors and binary file compares
	if (!diffSuccess)
		nResult = RESCAN_FILE_ERR;
	else if (status.bBinaries)
	{
		bBinary = TRUE;
	}
	else
	{
		// Now update views and buffers for ghost lines

		// Prevent displaying views during this update 
		// BTW, this solves the problem of double asserts
		// (during the display of an assert message box, a second assert in one of the 
		//  display functions happens, and hides the first assert)
		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			m_pView[nBuffer]->DetachFromBuffer();
			m_pDetailView[nBuffer]->DetachFromBuffer();
		}

		// Remove blank lines and clear winmerge flags
		// this operation does not change the modified flag
		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
			m_ptBuf[nBuffer]->prepareForRescan();

		// Divide diff blocks to match lines.
		if (GetOptionsMgr()->GetBool(OPT_CMP_MATCH_SIMILAR_LINES) && m_nBuffers < 3)
			AdjustDiffBlocks();

		// Analyse diff-list (updating real line-numbers)
		// this operation does not change the modified flag
		PrimeTextBuffers();

		// Hide identical lines if diff-context is not 'All'
		HideLines();

		// Apply flags to lines that moved, to differentiate from appeared/disappeared lines
		if (m_diffWrapper.GetDetectMovedBlocks())
			FlagMovedLines();
		
		// After PrimeTextBuffers() we know amount of real diffs
		// (m_nDiffs) and trivial diffs (m_nTrivialDiffs)

		// Identical files are also updated
		if (!m_diffList.HasSignificantDiffs())
			identical = IDENTLEVEL_ALL;

		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			// just apply some options to the views
			m_pView[nBuffer]->PrimeListWithFile();
			m_pDetailView[nBuffer]->PrimeListWithFile();

			// Now buffers data are valid
			m_pView[nBuffer]->ReAttachToBuffer();
			m_pDetailView[nBuffer]->ReAttachToBuffer();

			m_bEditAfterRescan[nBuffer] = FALSE;
		}
	}

	GetParentFrame()->SetLastCompareResult(m_diffList.GetSignificantDiffs());

	return nResult;
}

void CMergeDoc::CheckFileChanged(void)
{
	int nBuffer;
	DiffFileInfo fileInfo;
	BOOL bFileChanged[3];

	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		bFileChanged[nBuffer] = IsFileChangedOnDisk(m_filePaths[nBuffer].c_str(), fileInfo,
			FALSE, nBuffer);

		m_pRescanFileInfo[nBuffer]->Update((LPCTSTR)m_filePaths[nBuffer].c_str());
	}

	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		if (bFileChanged[nBuffer])
		{
			CString msg;
			LangFormatString1(msg, IDS_FILECHANGED_RESCAN, m_filePaths[nBuffer].c_str());
			int ans = AfxMessageBox(msg, MB_YESNO | MB_ICONWARNING);
			if (ans == IDYES)
			{
				OnFileReload();
				break;
			}
			break;
		}
	}
}


/** @brief Adjust all different lines that were detected as actually matching moved lines */
void CMergeDoc::FlagMovedLines(void)
{
	int i;
	MovedLines *pMovedLines;

	pMovedLines = m_diffWrapper.GetMovedLines(0);
	for (i = 0; i < m_ptBuf[0]->GetLineCount(); ++i)
	{
		int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_RIGHT);
		if (j != -1)
		{
			TRACE(_T("%d->%d\n"), i, j);
			ASSERT(j>=0);
			// We only flag lines that are already marked as being different
			int apparent = m_ptBuf[0]->ComputeApparentLine(i);
			if (m_ptBuf[0]->FlagIsSet(apparent, LF_DIFF))
			{
				m_ptBuf[0]->SetLineFlag(apparent, LF_MOVED, TRUE, FALSE, FALSE);
			}
		}
	}

	pMovedLines = m_diffWrapper.GetMovedLines(1);
	for (i=0; i<m_ptBuf[1]->GetLineCount(); ++i)
	{
		int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_LEFT);
		if (j != -1)
		{
			TRACE(_T("%d->%d\n"), i, j);
			ASSERT(j>=0);
			// We only flag lines that are already marked as being different
			int apparent = m_ptBuf[1]->ComputeApparentLine(i);
			if (m_ptBuf[1]->FlagIsSet(apparent, LF_DIFF))
			{
				m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, TRUE, FALSE, FALSE);
			}
		}
	}

	if (m_nBuffers < 3)
		return;

	pMovedLines = m_diffWrapper.GetMovedLines(1);
	for (i=0; i<m_ptBuf[1]->GetLineCount(); ++i)
	{
		int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_RIGHT);
		if (j != -1)
		{
			TRACE(_T("%d->%d\n"), i, j);
			ASSERT(j>=0);
			// We only flag lines that are already marked as being different
			int apparent = m_ptBuf[1]->ComputeApparentLine(i);
			if (m_ptBuf[1]->FlagIsSet(apparent, LF_DIFF))
			{
				m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, TRUE, FALSE, FALSE);
			}
		}
	}

	pMovedLines = m_diffWrapper.GetMovedLines(2);
	for (i=0; i<m_ptBuf[2]->GetLineCount(); ++i)
	{
		int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_LEFT);
		if (j != -1)
		{
			TRACE(_T("%d->%d\n"), i, j);
			ASSERT(j>=0);
			// We only flag lines that are already marked as being different
			int apparent = m_ptBuf[2]->ComputeApparentLine(i);
			if (m_ptBuf[2]->FlagIsSet(apparent, LF_DIFF))
			{
				m_ptBuf[2]->SetLineFlag(apparent, LF_MOVED, TRUE, FALSE, FALSE);
			}
		}
	}

	// todo: Need to record actual moved information
}

/**
 * @brief Prints (error) message by rescan status.
 *
 * @param nRescanResult [in] Resultcocode from rescan().
 * @param bIdentical [in] Were files identical?.
 * @sa CMergeDoc::Rescan()
 */
void CMergeDoc::ShowRescanError(int nRescanResult, IDENTLEVEL identical)
{
	// Rescan was suppressed, there is no sensible status
	if (nRescanResult == RESCAN_SUPPRESSED)
		return;

	String s;

	if (nRescanResult == RESCAN_FILE_ERR)
	{
		s = theApp.LoadString(IDS_FILEERROR);
		LogErrorString(s.c_str());
		AfxMessageBox(s.c_str(), MB_ICONSTOP);
		return;
	}

	if (nRescanResult == RESCAN_TEMP_ERR)
	{
		s = theApp.LoadString(IDS_TEMP_FILEERROR);
		LogErrorString(s.c_str());
		AfxMessageBox(s.c_str(), MB_ICONSTOP);
		return;
	}

	// Files are not binaries, but they are identical
	if (identical != IDENTLEVEL_NONE)
	{
		if (!m_filePaths.GetLeft().empty() && !m_filePaths.GetMiddle().empty() && !m_filePaths.GetRight().empty() && 
			m_filePaths.GetLeft() == m_filePaths.GetRight() && m_filePaths.GetMiddle() == m_filePaths.GetRight())
		{
			// compare file to itself, a custom message so user may hide the message in this case only
			s = theApp.LoadString(IDS_FILE_TO_ITSELF);
			AfxMessageBox(s.c_str(), MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN, IDS_FILE_TO_ITSELF);
		}
		else if (identical == IDENTLEVEL_ALL)
		{
			UINT nFlags = MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN;

			// Show the "files are identical" error message even if the user
			// requested not to show it again. It is better than to close
			// the application without a warning.
			if (GetMainFrame()->m_bExitIfNoDiff)
			{
				nFlags &= ~MB_DONT_DISPLAY_AGAIN;
			}

			LangMessageBox(IDS_FILESSAME, nFlags);

			// Exit application if files are identical.
			if (GetMainFrame()->m_bExitIfNoDiff)
			{
				GetMainFrame()->PostMessage(WM_COMMAND, ID_APP_EXIT);
			}
		}
	}
}

CMergeDoc::CDiffTextBuffer::CDiffTextBuffer(CMergeDoc * pDoc, int pane)
: m_pOwnerDoc(pDoc)
, m_nThisPane(pane)
, unpackerSubcode(0)
{
}

BOOL CMergeDoc::CDiffTextBuffer::GetLine(int nLineIndex, CString &strLine)
{ 
	int nLineLength = CCrystalTextBuffer::GetLineLength 
		( nLineIndex ); 
	
	if( nLineLength < 0 ) 
		return FALSE; 
	else if( nLineLength == 0 ) 
		strLine.Empty(); 
	else 
	{ 
		_tcsncpy ( strLine.GetBuffer( nLineLength + 1 ), 
			CCrystalTextBuffer::GetLineChars( nLineIndex ), 
			nLineLength ); 
		strLine.ReleaseBuffer( nLineLength ); 
	} 
	return TRUE; 
}

void CMergeDoc::CDiffTextBuffer::SetModified(BOOL bModified /*= TRUE*/)
{
	CCrystalTextBuffer::SetModified (bModified);
	m_pOwnerDoc->SetModifiedFlag (bModified);
}

BOOL CMergeDoc::CDiffTextBuffer::GetFullLine(int nLineIndex, CString &strLine)
{
	int cchText = GetFullLineLength(nLineIndex);
	if (cchText == 0)
		return FALSE;
	LPTSTR pchText = strLine.GetBufferSetLength(cchText);
	memcpy(pchText, GetLineChars(nLineIndex), cchText * sizeof(TCHAR));
	return TRUE;
}

void CMergeDoc::CDiffTextBuffer::AddUndoRecord(BOOL bInsert, const CPoint & ptStartPos, const CPoint & ptEndPos, LPCTSTR pszText, int cchText, int nLinesToValidate, int nActionType /*= CE_ACTION_UNKNOWN*/, CDWordArray *paSavedRevisonNumbers)
{
	CGhostTextBuffer::AddUndoRecord(bInsert, ptStartPos, ptEndPos, pszText, cchText, nLinesToValidate, nActionType, paSavedRevisonNumbers);
	if (m_aUndoBuf[m_nUndoPosition - 1].m_dwFlags & UNDO_BEGINGROUP)
	{
		m_pOwnerDoc->undoTgt.erase(m_pOwnerDoc->curUndo, m_pOwnerDoc->undoTgt.end());
		m_pOwnerDoc->undoTgt.push_back(m_pOwnerDoc->m_pView[m_nThisPane]);
		m_pOwnerDoc->curUndo = m_pOwnerDoc->undoTgt.end();
	}
}

BOOL CMergeDoc::Undo()
{
	return FALSE;
}

/**
 * @brief An instance of RescanSuppress prevents rescan during its lifetime
 * (or until its Clear method is called, which ends its effect).
 */
class RescanSuppress
{
public:
	RescanSuppress(CMergeDoc & doc) : m_doc(doc)
	{
		m_bSuppress = TRUE;
		m_bPrev = doc.m_bEnableRescan;
		doc.m_bEnableRescan = FALSE;
	}
	void Clear() 
	{
		if (m_bSuppress)
		{
			m_bSuppress = FALSE;
			m_doc.m_bEnableRescan = m_bPrev;
		}
	}
	~RescanSuppress()
	{
		Clear();
	}
private:
	CMergeDoc & m_doc;
	BOOL m_bPrev;
	BOOL m_bSuppress;
};

/**
 * @brief Copy all diffs from one side to side.
 * @param [in] srcPane Source side from which diff is copied
 * @param [in] dstPane Destination side
 */
void CMergeDoc::CopyAllList(int srcPane, int dstPane)
{
	CopyMultipleList(srcPane, dstPane, 0, m_diffList.GetSize() - 1);
}

/**
 * @brief Copy range of diffs from one side to side.
 * This function copies given range of differences from side to another.
 * Ignored differences are skipped, and not copied.
 * @param [in] srcPane Source side from which diff is copied
 * @param [in] dstPane Destination side
 * @param [in] firstDiff First diff copied (0-based index)
 * @param [in] lastDiff Last diff copied (0-based index)
 */
void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int lastDiff)
{
#ifdef _DEBUG
	if (firstDiff > lastDiff)
		_RPTF0(_CRT_ERROR, "Invalid diff range (firstDiff > lastDiff)!");
	if (firstDiff < 0)
		_RPTF0(_CRT_ERROR, "Invalid diff range (firstDiff < 0)!");
	if (lastDiff > m_diffList.GetSize() - 1)
		_RPTF0(_CRT_ERROR, "Invalid diff range (lastDiff < diffcount)!");
#endif

	lastDiff = min(m_diffList.GetSize() - 1, lastDiff);
	firstDiff = max(0, firstDiff);
	if (firstDiff > lastDiff)
		return;
	
	RescanSuppress suppressRescan(*this);

	// Note we don't care about m_nDiffs count to become zero,
	// because we don't rescan() so it does not change

	SetCurrentDiff(lastDiff);
	bool bGroupWithPrevious = false;
	if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, true))
		return; // sync failure

	SetEditedAfterRescan(dstPane);

	CPoint currentPosSrc = m_pView[srcPane]->GetCursorPos();
	currentPosSrc.x = 0;
	CPoint currentPosDst = m_pView[dstPane]->GetCursorPos();
	currentPosDst.x = 0;

	// copy from bottom up is more efficient
	for (int i = lastDiff - 1; i >= firstDiff; --i)
	{
		if (m_diffList.IsDiffSignificant(i))
		{
			const DIFFRANGE *pdi = m_diffList.DiffRangeAt(i);
			if (currentPosDst.y > pdi->dend[dstPane])
			{
				if (pdi->blank[dstPane] >= 0)
					currentPosDst.y -= pdi->dend[dstPane] - pdi->blank[dstPane] + 1;
				else if (pdi->blank[srcPane] >= 0)
					currentPosDst.y -= pdi->dend[dstPane] - pdi->blank[srcPane] + 1;
			}			
			SetCurrentDiff(i);
			// Group merge with previous (merge undo data to one action)
			bGroupWithPrevious = true;
			if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false))
				break; // sync failure
		}
	}

	m_pView[dstPane]->SetCursorPos(currentPosDst);
	m_pView[dstPane]->SetNewSelection(currentPosDst, currentPosDst, false);
	m_pView[dstPane]->SetNewAnchor(currentPosDst);
	m_pDetailView[dstPane]->SetCursorPos(currentPosDst);
	m_pDetailView[dstPane]->SetNewSelection(currentPosDst, currentPosDst, false);
	m_pDetailView[dstPane]->SetNewAnchor(currentPosDst);

	suppressRescan.Clear(); // done suppress Rescan
	FlushAndRescan();
}

/**
 * @brief Sanity check difference.
 *
 * Checks that lines in difference are inside difference in both files.
 * If file is edited, lines added or removed diff lines get out of sync and
 * merging fails miserably.
 *
 * @param [in] dr Difference to check.
 * @return TRUE if difference lines match, FALSE otherwise.
 */
BOOL CMergeDoc::SanityCheckDiff(DIFFRANGE dr)
{
	int cd_dbegin = dr.dbegin[0];
	int cd_dend = dr.dend[0];
	int nBuffer;

	// Must ensure line number is in range before getting line flags
	if (cd_dend >= m_ptBuf[0]->GetLineCount())
		return FALSE;
	DWORD dwLeftFlags = m_ptBuf[0]->GetLineFlags(cd_dend);

	// Must ensure line number is in range before getting line flags
	if (cd_dend >= m_ptBuf[1]->GetLineCount())
		return FALSE;
	DWORD dwRightFlags = m_ptBuf[1]->GetLineFlags(cd_dend);

	// Optimization - check last line first so we don't need to
	// check whole diff for obvious cases
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		DWORD dwFlags = m_ptBuf[nBuffer]->GetLineFlags(cd_dend);
		if (!(dwFlags & LF_WINMERGE_FLAGS))
			return FALSE;
	}

	for (int line = cd_dbegin; line < cd_dend; line++)
	{
		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			DWORD dwFlags = m_ptBuf[nBuffer]->GetLineFlags(cd_dend);
			if (!(dwFlags & LF_WINMERGE_FLAGS))
				return FALSE;
		}
	}
	return TRUE;
}

/**
 * @brief Copy selected (=current) difference from from side to side.
 * @param [in] srcPane Source side from which diff is copied
 * @param [in] dstPane Destination side
 * @param [in] nDiff Diff to copy, if -1 function determines it.
 * @param [in] bGroupWithPrevious Adds diff to same undo group with
 * @return true if ok, false if sync failure & need to abort copy
 * previous action (allows one undo for copy all)
 */
bool CMergeDoc::ListCopy(int srcPane, int dstPane, int nDiff /* = -1*/,
		bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/)
{
	CCrystalTextView *pSource = bUpdateView ? m_pView[dstPane] : NULL;

	// suppress Rescan during this method
	// (Not only do we not want to rescan a lot of times, but
	// it will wreck the line status array to rescan as we merge)
	RescanSuppress suppressRescan(*this);

	// If diff-number not given, determine it from active view
	if (nDiff == -1)
	{
		nDiff = GetCurrentDiff();

		// No current diff, but maybe cursor is in diff?
		if (nDiff == -1 && (m_pView[srcPane]->IsCursorInDiff() ||
			m_pView[dstPane]->IsCursorInDiff()))
		{
			// Find out diff under cursor
			CPoint ptCursor;
			int nActiveViewIndexType = GetActiveMergeViewIndexType();
			if (nActiveViewIndexType >= MERGEVIEW_PANE0 && nActiveViewIndexType <= MERGEVIEW_PANE2)
				ptCursor = m_pView[nActiveViewIndexType]->GetCursorPos();
			else if (nActiveViewIndexType >= MERGEVIEW_PANE0_DETAIL ||
					nActiveViewIndexType <= MERGEVIEW_PANE2_DETAIL)
			{
				ptCursor = m_pView[nActiveViewIndexType - MERGEVIEW_PANE0_DETAIL]->GetCursorPos();
			}
			nDiff = m_diffList.LineToDiff(ptCursor.y);
		}
	}

	if (nDiff != -1)
	{
		DIFFRANGE cd;
		VERIFY(m_diffList.GetDiff(nDiff, cd));
		CDiffTextBuffer& sbuf = *m_ptBuf[srcPane];
		CDiffTextBuffer& dbuf = *m_ptBuf[dstPane];
		BOOL bSrcWasMod = sbuf.IsModified();
		int cd_dbegin = cd.dbegin[srcPane];
		int cd_dend = cd.dend[srcPane];
		int cd_blank = cd.blank[srcPane];
		BOOL bInSync = SanityCheckDiff(cd);

		if (bInSync == FALSE)
		{
			LangMessageBox(IDS_VIEWS_OUTOFSYNC, MB_ICONSTOP);
			return false; // abort copying
		}

		// If we remove whole diff from current view, we must fix cursor
		// position first. Normally we would move to end of previous line,
		// but we want to move to begin of that line for usability.
		if (bUpdateView)
		{
			CPoint currentPos = m_pView[dstPane]->GetCursorPos();
			currentPos.x = 0;
			if (currentPos.y > cd_dend)
			{
				if (cd.blank[dstPane] >= 0)
					currentPos.y -= cd_dend - cd.blank[dstPane] + 1;
				else if (cd.blank[srcPane] >= 0)
					currentPos.y -= cd_dend - cd.blank[srcPane] + 1;
			}
			m_pView[dstPane]->SetCursorPos(currentPos);
			m_pDetailView[dstPane]->SetCursorPos(currentPos);
		}

		// if the current diff contains missing lines, remove them from both sides
		int limit = cd_dend;

		// curView is the view which is changed, so the opposite of the source view
		CCrystalTextView* srcView = m_pView[srcPane];
		CCrystalTextView* dstView = m_pView[dstPane];

		dbuf.BeginUndoGroup(bGroupWithPrevious);
		if (cd_blank>=0)
		{
			// text was missing, so delete rest of lines on both sides
			// delete only on destination side since rescan will clear the other side
			if (cd_dend + 1 < dbuf.GetLineCount())
			{
				dbuf.DeleteText(pSource, cd_blank, 0, cd_dend+1, 0, CE_ACTION_MERGE);
			}
			else
			{
				// To removing EOL chars of last line, deletes from the end of the line (cd_blank - 1).
				ASSERT(cd_blank > 0);
				dbuf.DeleteText(pSource, cd_blank-1, dbuf.GetLineLength(cd_blank-1), cd_dend, dbuf.GetLineLength(cd_dend), CE_ACTION_MERGE);
			}

			limit=cd_blank-1;
			dbuf.FlushUndoGroup(pSource);
			dbuf.BeginUndoGroup(TRUE);
		}


		// copy the selected text over
		if (cd_dbegin <= limit)
		{
			// text exists on left side, so just replace
			dbuf.ReplaceFullLines(dbuf, sbuf, pSource, cd_dbegin, limit, CE_ACTION_MERGE);
			dbuf.FlushUndoGroup(pSource);
			dbuf.BeginUndoGroup(TRUE);
		}
		dbuf.FlushUndoGroup(pSource);

		// remove the diff
		SetCurrentDiff(-1);

		// reset the mod status of the source view because we do make some
		// changes, but none that concern the source text
		sbuf.SetModified(bSrcWasMod);
	}

	suppressRescan.Clear(); // done suppress Rescan
	FlushAndRescan();
	return true;
}

/**
 * @brief Save file with new filename.
 *
 * This function is called by CMergeDoc::DoSave() or CMergeDoc::DoSAveAs()
 * to save file with new filename. CMergeDoc::DoSave() calls if saving with
 * normal filename fails, to let user choose another filename/location.
 * Also, if file is unnamed file (e.g. scratchpad) then it must be saved
 * using this function.
 * @param [in, out] strPath 
 * - [in] : Initial path shown to user
 * - [out] : Path to new filename if saving succeeds
 * @param [in, out] nSaveResult 
 * - [in] : Statuscode telling why we ended up here. Maybe the result of
 * previous save.
 * - [out] : Statuscode of this saving try
 * @param [in, out] sError Error string from lower level saving code
 * @param [in] nBuffer Buffer we are saving
 * @return FALSE as long as the user is not satisfied. Calling function
 * should not continue until TRUE is returned.
 * @sa CMergeDoc::DoSave()
 * @sa CMergeDoc::DoSaveAs()
 * @sa CMergeDoc::CDiffTextBuffer::SaveToFile()
 */
BOOL CMergeDoc::TrySaveAs(CString &strPath, int &nSaveResult, CString & sError,
	int nBuffer, PackingInfo * pInfoTempUnpacker)
{
	CString s;
	CString strSavePath; // New path for next saving try
	UINT titleid = 0;
	BOOL result = TRUE;
	int answer = IDOK; // Set default we use for scratchpads
	int nActiveViewIndexType = GetActiveMergeViewIndexType();

	if (nActiveViewIndexType == -1)
	{
		// We don't have valid view active, but still tried to save.
		// We don't know which file to save, so just cancel.
		// Possible origin in location pane?
		_RPTF0(_CRT_ERROR, "Save request from invalid view!");
		nSaveResult = SAVE_CANCELLED;
		return TRUE;
	}

	HWND parent = m_pView[nActiveViewIndexType]->GetSafeHwnd();

	// We shouldn't get here if saving is succeed before
	ASSERT(nSaveResult != SAVE_DONE);

	// Select message based on reason function called
	if (nSaveResult == SAVE_PACK_FAILED)
	{
		if (m_nBuffers == 3)
		{
			LangFormatString2(s, IDS_FILEPACK_FAILED_LEFT + nBuffer,
				strPath, pInfoTempUnpacker->pluginName.c_str());
		}
		else
		{
			LangFormatString2(s, nBuffer == 0 ? IDS_FILEPACK_FAILED_LEFT : IDS_FILEPACK_FAILED_RIGHT,
				strPath, pInfoTempUnpacker->pluginName.c_str());
		}
		// replace the unpacker with a "do nothing" unpacker
		pInfoTempUnpacker->Initialize(PLUGIN_MANUAL);
	}
	else
	{
		LangFormatString2(s, IDS_FILESAVE_FAILED, strPath, sError);
	}

	// SAVE_NO_FILENAME is temporarily used for scratchpad.
	// So don't ask about saving in that case.
	if (nSaveResult != SAVE_NO_FILENAME)
		answer = AfxMessageBox(s, MB_OKCANCEL | MB_ICONWARNING);

	switch (answer)
	{
	case IDOK:
		if (nBuffer == 0)
			titleid = IDS_SAVE_LEFT_AS;
		else if (nBuffer == m_nBuffers - 1)
			titleid = IDS_SAVE_RIGHT_AS;
		else
			titleid = IDS_SAVE_MIDDLE_AS;

		if (SelectFile(parent, s, strPath, titleid, NULL, FALSE))
		{
			CDiffTextBuffer *pBuffer = m_ptBuf[nBuffer];
			strSavePath = s;
			nSaveResult = pBuffer->SaveToFile(strSavePath, FALSE, sError,
				pInfoTempUnpacker);

			if (nSaveResult == SAVE_DONE)
			{
				// We are saving scratchpad (unnamed file)
				if (strPath.IsEmpty())
				{
					m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
					m_strDesc[nBuffer].erase();
				}
					
				strPath = strSavePath;
				UpdateHeaderPath(nBuffer);
			}
			else
				result = FALSE;
		}
		else
			nSaveResult = SAVE_CANCELLED;
		break;

	case IDCANCEL:
		nSaveResult = SAVE_CANCELLED;
		break;
	}
	return result;
}

/**
 * @brief Save file creating backups etc.
 *
 * Safe top-level file saving function. Checks validity of given path.
 * Creates backup file if wanted to. And if saving to given path fails,
 * allows user to select new location/name for file.
 * @param [in] szPath Path where to save including filename. Can be
 * empty/NULL if new file is created (scratchpad) without filename.
 * @param [out] bSaveSuccess Will contain information about save success with
 * the original name (to determine if file statuses should be changed)
 * @param [in] nBuffer Index (0-based) of buffer to save
 * @return Tells if caller can continue (no errors happened)
 * @note Return value does not tell if SAVING succeeded. Caller must
 * Check value of bSaveSuccess parameter after calling this function!
 * @note If CMainFrame::m_strSaveAsPath is non-empty, file is saved
 * to directory it points to. If m_strSaveAsPath contains filename,
 * that filename is used.
 * @sa CMergeDoc::TrySaveAs()
 * @sa CMainFrame::CheckSavePath()
 * @sa CMergeDoc::CDiffTextBuffer::SaveToFile()
 */
BOOL CMergeDoc::DoSave(LPCTSTR szPath, BOOL &bSaveSuccess, int nBuffer)
{
	DiffFileInfo fileInfo;
	CString strSavePath(szPath);
	CString sError;
	BOOL bFileChanged = FALSE;
	BOOL bApplyToAll = FALSE;	
	int nRetVal = -1;

	bFileChanged = IsFileChangedOnDisk(szPath, fileInfo, TRUE, nBuffer);
	if (bFileChanged)
	{
		CString msg;
		LangFormatString1(msg, IDS_FILECHANGED_ONDISK, szPath);
		if (AfxMessageBox(msg, MB_ICONWARNING | MB_YESNO) == IDNO)
		{
			bSaveSuccess = SAVE_CANCELLED;
			return TRUE;
		}		
	}

	// use a temp packer
	// first copy the m_pInfoUnpacker
	// if an error arises during packing, change and take a "do nothing" packer
	PackingInfo infoTempUnpacker = *m_pInfoUnpacker;

	bSaveSuccess = FALSE;
	
	// Check third arg possibly given from command-line
	if (!GetMainFrame()->m_strSaveAsPath.IsEmpty())
	{
		if (paths_DoesPathExist(GetMainFrame()->m_strSaveAsPath) == IS_EXISTING_DIR)
		{
			// third arg was a directory, so get append the filename
			String sname;
			SplitFilename(szPath, 0, &sname, 0);
			strSavePath = GetMainFrame()->m_strSaveAsPath;
			if (GetMainFrame()->m_strSaveAsPath.Right(1) != _T('\\'))
				strSavePath += _T('\\');
			strSavePath += sname.c_str();
		}
		else
			strSavePath = GetMainFrame()->m_strSaveAsPath;	
	}

	nRetVal = GetMainFrame()->HandleReadonlySave(strSavePath, FALSE, bApplyToAll);
	if (nRetVal == IDCANCEL)
		return FALSE;

	if (!GetMainFrame()->CreateBackup(FALSE, strSavePath))
		return FALSE;

	// FALSE as long as the user is not satisfied
	// TRUE if saving succeeds, even with another filename, or if the user cancels
	BOOL result = FALSE;
	// the error code from the latest save operation, 
	// or SAVE_DONE when the save succeeds
	// TODO: Shall we return this code in addition to bSaveSuccess ?
	int nSaveErrorCode = SAVE_DONE;
	CDiffTextBuffer *pBuffer = m_ptBuf[nBuffer];

	// Assume empty filename means Scratchpad (unnamed file)
	// Todo: This is not needed? - buffer type check should be enough
	if (strSavePath.IsEmpty())
		nSaveErrorCode = SAVE_NO_FILENAME;

	// Handle unnamed buffers
	if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
		nSaveErrorCode = SAVE_NO_FILENAME;

	if (nSaveErrorCode == SAVE_DONE)
		// We have a filename, just try to save
		nSaveErrorCode = pBuffer->SaveToFile(strSavePath, FALSE, sError, &infoTempUnpacker);

	if (nSaveErrorCode != SAVE_DONE)
	{
		// Saving failed, user may save to another location if wants to
		do
			result = TrySaveAs(strSavePath, nSaveErrorCode, sError, nBuffer, &infoTempUnpacker);
		while (!result);
	}

	// Saving succeeded with given/selected filename
	if (nSaveErrorCode == SAVE_DONE)
	{
		// Preserve file times if user wants to
		if (GetOptionsMgr()->GetBool(OPT_PRESERVE_FILETIMES))
		{
			fileInfo.SetFile((LPCTSTR)strSavePath);
			files_UpdateFileTime(fileInfo);
		}

		m_ptBuf[nBuffer]->SetModified(FALSE);
		m_pSaveFileInfo[nBuffer]->Update((LPCTSTR)strSavePath);
		m_pRescanFileInfo[nBuffer]->Update((LPCTSTR)m_filePaths[nBuffer].c_str());
		m_filePaths[nBuffer] = strSavePath;
		UpdateHeaderPath(nBuffer);
		bSaveSuccess = TRUE;
		result = TRUE;
	}
	else if (nSaveErrorCode == SAVE_CANCELLED)
	{
		// User cancelled current operation, lets do what user wanted to do
		result = FALSE;
	}
	return result;
}

/**
 * @brief Save file with different filename.
 *
 * Safe top-level file saving function. Asks user to select filename
 * and path. Does not create backups.
 * @param [in] szPath Path where to save including filename. Can be
 * empty/NULL if new file is created (scratchpad) without filename.
 * @param [out] bSaveSuccess Will contain information about save success with
 * the original name (to determine if file statuses should be changed)
 * @param [in] nBuffer Index (0-based) of buffer to save
 * @return Tells if caller can continue (no errors happened)
 * @note Return value does not tell if SAVING succeeded. Caller must
 * Check value of bSaveSuccess parameter after calling this function!
 * @sa CMergeDoc::TrySaveAs()
 * @sa CMainFrame::CheckSavePath()
 * @sa CMergeDoc::CDiffTextBuffer::SaveToFile()
 */
BOOL CMergeDoc::DoSaveAs(LPCTSTR szPath, BOOL &bSaveSuccess, int nBuffer)
{
	CString strSavePath(szPath);
	CString sError;

	// use a temp packer
	// first copy the m_pInfoUnpacker
	// if an error arises during packing, change and take a "do nothing" packer
	PackingInfo infoTempUnpacker = *m_pInfoUnpacker;

	bSaveSuccess = FALSE;
	// FALSE as long as the user is not satisfied
	// TRUE if saving succeeds, even with another filename, or if the user cancels
	BOOL result = FALSE;
	// the error code from the latest save operation, 
	// or SAVE_DONE when the save succeeds
	// TODO: Shall we return this code in addition to bSaveSuccess ?
	int nSaveErrorCode = SAVE_DONE;

	// Use SAVE_NO_FILENAME to prevent asking about error
	nSaveErrorCode = SAVE_NO_FILENAME;

	// Loop until user succeeds saving or cancels
	do
		result = TrySaveAs(strSavePath, nSaveErrorCode, sError, nBuffer, &infoTempUnpacker);
	while (!result);

	// Saving succeeded with given/selected filename
	if (nSaveErrorCode == SAVE_DONE)
	{
		m_pSaveFileInfo[nBuffer]->Update((LPCTSTR)strSavePath);
		m_pRescanFileInfo[nBuffer]->Update((LPCTSTR)m_filePaths[nBuffer].c_str());
		m_filePaths[nBuffer] = strSavePath;
		UpdateHeaderPath(nBuffer);
		bSaveSuccess = TRUE;
		result = TRUE;
	}
	return result;
}

/**
 * @brief Get left->right info for a moved line (apparent line number)
 */
int CMergeDoc::RightLineInMovedBlock(int nBuffer, int apparentLeftLine)
{
	if (!(m_ptBuf[nBuffer]->GetLineFlags(apparentLeftLine) & LF_MOVED))
		return -1;

	int realLeftLine = m_ptBuf[nBuffer]->ComputeRealLine(apparentLeftLine);
	int realRightLine = -1;
	if (m_diffWrapper.GetDetectMovedBlocks())
	{
		realRightLine = m_diffWrapper.GetMovedLines(nBuffer)->LineInBlock(realLeftLine,
				MovedLines::SIDE_RIGHT);
	}
	if (realRightLine != -1)
		return m_ptBuf[nBuffer + 1]->ComputeApparentLine(realRightLine);
	else
		return -1;
}

/**
 * @brief Get right->left info for a moved line (apparent line number)
 */
int CMergeDoc::LeftLineInMovedBlock(int nBuffer, int apparentRightLine)
{
	if (!(m_ptBuf[nBuffer]->GetLineFlags(apparentRightLine) & LF_MOVED))
		return -1;

	int realRightLine = m_ptBuf[nBuffer]->ComputeRealLine(apparentRightLine);
	int realLeftLine = -1;
	if (m_diffWrapper.GetDetectMovedBlocks())
	{
		realLeftLine = m_diffWrapper.GetMovedLines(nBuffer)->LineInBlock(realRightLine,
				MovedLines::SIDE_LEFT);
	}
	if (realLeftLine != -1)
		return m_ptBuf[nBuffer - 1]->ComputeApparentLine(realLeftLine);
	else
		return -1;
}

/**
 * @brief Save modified documents.
 * This function asks if user wants to save modified documents. We also
 * allow user to cancel the closing.
 *
 * There is a special trick avoiding showing two save-dialogs, as MFC framework
 * sometimes calls this function twice. We use static counter for these calls
 * and if we already have saving in progress (counter == 1) we skip the new
 * saving dialog.
 *
 * @return TRUE if docs are closed, FALSE if closing is cancelled.
 */
BOOL CMergeDoc::SaveModified()
{
	static int counter;
	++counter;
	if (counter > 1)
		return FALSE;

	if (PromptAndSaveIfNeeded(TRUE))
	{
		counter = 0;
		return TRUE;
	}
	else
	{
		counter = 0;
		return FALSE;
	}
}

/**
 * @brief Sets the current difference.
 * @param [in] nDiff Difference to set as current difference.
 */
void CMergeDoc::SetCurrentDiff(int nDiff)
{
	if (nDiff >= 0 && nDiff <= m_diffList.LastSignificantDiff())
		m_nCurDiff = nDiff;
	else
		m_nCurDiff = -1;
}

/**
 * @brief Checks if a flag is set for line.
 * @param [in] line Index (0-based) for line.
 * @param [in] flag Flag to check.
 * @return TRUE if flag is set, FALSE otherwise.
 */
BOOL CMergeDoc::CDiffTextBuffer::FlagIsSet(UINT line, DWORD flag)
{
	return ((m_aLines[line].m_dwFlags & flag) == flag);
}


/**
Remove blank lines and clear winmerge flags
(2003-06-21, Perry: I don't understand why this is necessary, but if this isn't 
done, more and more gray lines appear in the file)
(2003-07-31, Laoran I don't understand either why it is necessary, but it works
fine, so let's go on with it)
*/
void CMergeDoc::CDiffTextBuffer::prepareForRescan()
{
	RemoveAllGhostLines();
	for(int ct=GetLineCount()-1; ct>=0; --ct)
	{
		SetLineFlag(ct, 
			LF_INVISIBLE | LF_DIFF | LF_TRIVIAL | LF_MOVED | LF_SNP,
			FALSE, FALSE, FALSE);
	}
}


void CMergeDoc::CDiffTextBuffer::OnNotifyLineHasBeenEdited(int nLine)
{
	SetLineFlag(nLine, LF_DIFF, FALSE, FALSE, FALSE);
	SetLineFlag(nLine, LF_TRIVIAL, FALSE, FALSE, FALSE);
	SetLineFlag(nLine, LF_MOVED, FALSE, FALSE, FALSE);
	SetLineFlag(nLine, LF_SNP, FALSE, FALSE, FALSE);
	CGhostTextBuffer::OnNotifyLineHasBeenEdited(nLine);
}



/**
 * @brief Helper to determine the most used CRLF mode in the file
 * Normal call : determine the CRLF mode of the current line
 * Final call  : parameter lpLineBegin = NULL, return the style of the file
 *
 * @note  The lowest CRL_STYLE has the priority in case of equality
 */
int CMergeDoc::CDiffTextBuffer::NoteCRLFStyleFromBuffer(TCHAR *lpLineBegin, DWORD dwLineLen /* =0 */)
{
	static int count[3] = {0};
	int iStyle = -1;

	// give back the result when we ask for it
	if (lpLineBegin == NULL)
	{
		iStyle = 0;
		if (count[1] > count[iStyle])
			iStyle = 1;
		if (count[2] > count[iStyle])
			iStyle = 2;

		// reset the counter for the next file
		count[0] = count[1] = count[2] = 0;

		return iStyle;
	}

	if (dwLineLen >= 1)
	{
		if (lpLineBegin[dwLineLen-1] == _T('\r'))
			iStyle = CRLF_STYLE_MAC;
		if (lpLineBegin[dwLineLen-1] == _T('\n'))
			iStyle = CRLF_STYLE_UNIX;
	}
	if (dwLineLen >= 2)
	{
		if (lpLineBegin[dwLineLen-2] == _T('\r') && lpLineBegin[dwLineLen-1] == _T('\n'))
			iStyle = CRLF_STYLE_DOS;
	}
	ASSERT (iStyle != -1);
	count[iStyle] ++;
	return iStyle;
}

/// Reads one line from filebuffer and inserts to textbuffer
void CMergeDoc::CDiffTextBuffer::ReadLineFromBuffer(TCHAR *lpLineBegin, DWORD dwLineNum, DWORD dwLineLen /* =0 */)
{
	if (m_nSourceEncoding >= 0)
		iconvert (lpLineBegin, m_nSourceEncoding, 1, m_nSourceEncoding == 15);
	AppendLine(dwLineNum, lpLineBegin, dwLineLen);
}

/// Sets path for temporary files
void CMergeDoc::CDiffTextBuffer::SetTempPath(String path)
{
	m_strTempPath = path;
}

bool CMergeDoc::CDiffTextBuffer::IsInitialized() const
{
	return !!m_bInit;
}

/**
 * @brief Examine statistics in textFileStats and return a crystaltextbuffer enum value for line style
 */
int GetTextFileStyle(const UniMemFile::txtstats & stats)
{
	if (stats.ncrlfs >= stats.nlfs)
	{
		if (stats.ncrlfs >= stats.ncrs)
		{
			return CRLF_STYLE_DOS;
		}
		else
		{
			return CRLF_STYLE_MAC;
		}
	}
	else
	{
		if (stats.nlfs >= stats.ncrs)
		{
			return CRLF_STYLE_UNIX;
		}
		else
		{
			return CRLF_STYLE_MAC;
		}
	}
}
/**
 * @brief Examine statistics in textFileStats and tell if the file has only one EOL type
 */
int IsTextFileStylePure(const UniMemFile::txtstats & stats)
{
	int nType = 0;
	nType += (stats.ncrlfs > 0);
	nType += (stats.ncrs > 0);
	nType += (stats.nlfs > 0);
	return (nType <= 1);
}


/**
 * @brief Return a string giving #lines and #bytes and how much time elapsed.
 * @param [in] lines Count of lines.
 * @param [in] bytes Count of bytes.
 * @param [in] start Time used.
 * @return Formatted string.
 */
static CString GetLineByteTimeReport(UINT lines, __int64 bytes,
	const COleDateTime & start)
{
	String sLines = locality::NumToLocaleStr((int)lines);
	String sBytes = locality::NumToLocaleStr(bytes);
	COleDateTimeSpan duration = COleDateTime::GetCurrentTime() - start;
	String sMinutes = locality::NumToLocaleStr((int)duration.GetTotalMinutes());
	CString str;
	str.Format(_T("%s lines (%s byte) saved in %sm%02ds")
		, sLines.c_str(), sBytes.c_str(), sMinutes.c_str()
		, duration.GetSeconds()
		);
	return str;
}

/**
 * @brief Escape control characters.
 * @param [in,out] s Line of text excluding eol chars.
 *
 * @note Escape sequences follow the pattern
 * (leadin character, high nibble, low nibble, leadout character).
 * The leadin character is '\x0F'. The leadout character is a backslash.
 */
static void EscapeControlChars(CString &s)
{
	// Compute buffer length required for escaping
	int n = s.GetLength();
	LPCTSTR q = s;
	int i = n;
	while (i)
	{
		TCHAR c = q[--i];
		// Is it a control character in the range 0..31 except TAB?
		if (!(c & ~_T('\x1F')) && c != _T('\t'))
		{
			n += 3; // Need 3 extra characters to escape
		}
	}
	// Reallocate accordingly
	i = s.GetLength();
	LPTSTR p = s.GetBufferSetLength(n);
	// Copy/translate characters starting at end of string
	while (i)
	{
		TCHAR c = p[--i];
		// Is it a control character in the range 0..31 except TAB?
		if (!(c & ~_T('\x1F')) && c != _T('\t'))
		{
			// Bitwise OR with 0x100 so _itot() will output 3 hex digits
			_itot(0x100 | c, p + n - 4, 16);
			// Replace terminating zero with leadout character
			p[n - 1] = _T('\\');
			// Prepare to replace 1st hex digit with leadin character
			c = _T('\x0F');
			n -= 3;
		}
		p[--n] = c;
	}
}

/**
 * @brief Unescape control characters.
 * @param [in,out] s Line of text excluding eol chars.
 */
static void UnescapeControlChars(CString &s)
{
	int n = s.GetLength();
	LPTSTR p = s.LockBuffer();
	LPTSTR q = p;
	while ((*p = *q) != _T('\0'))
	{
		++q;
		// Is it the leadin character?
		if (*p == _T('\x0F'))
		{
			LPTSTR r = q;
			// Expect a hexadecimal number...
			long ordinal = (TCHAR)_tcstol(q, &r, 16);
			// ...followed by the leadout character.
			if (*r == _T('\\'))
			{
				*p = (TCHAR)ordinal;
				q = r + 1;
			}
		}
		++p;
	}
	s.ReleaseBuffer(p - s);
}

/**
 * @brief Load file from disk into buffer
 *
 * @param [in] pszFileNameInit File to load
 * @param [in] infoUnpacker Unpacker plugin
 * @param [in] sToFindUnpacker String for finding unpacker plugin
 * @param [out] readOnly Loading was lossy so file should be read-only
 * @param [in] nCrlfStyle EOL style used
 * @param [in] encoding Encoding used
 * @param [out] sError Error message returned
 * @return FRESULT_OK when loading succeed or (list in files.h):
 * - FRESULT_OK_IMPURE : load OK, but the EOL are of different types
 * - FRESULT_ERROR_UNPACK : plugin failed to unpack
 * - FRESULT_ERROR : loading failed, sError contains error message
 * - FRESULT_BINARY : file is binary file
 * @note If this method fails, it calls InitNew so the CDiffTextBuffer is in a valid state
 */
int CMergeDoc::CDiffTextBuffer::LoadFromFile(LPCTSTR pszFileNameInit,
		PackingInfo * infoUnpacker, LPCTSTR sToFindUnpacker, BOOL & readOnly,
		int nCrlfStyle, const FileTextEncoding & encoding, CString &sError)
{
	ASSERT(!m_bInit);
	ASSERT(m_aLines.GetSize() == 0);

	// Unpacking the file here, save the result in a temporary file
	String sFileName = pszFileNameInit;
	if (infoUnpacker->bToBeScanned)
	{
		if (!FileTransform_Unpacking(sFileName, sToFindUnpacker, infoUnpacker, &unpackerSubcode))
		{
			InitNew(); // leave crystal editor in valid, empty state
			return FileLoadResult::FRESULT_ERROR_UNPACK;
		}
	}
	else
	{
		if (!FileTransform_Unpacking(sFileName, infoUnpacker, &unpackerSubcode))
		{
			InitNew(); // leave crystal editor in valid, empty state
			return FileLoadResult::FRESULT_ERROR_UNPACK;
		}
	}
	// we use the same unpacker for both files, so it must be defined after first file
	ASSERT(infoUnpacker->bToBeScanned != PLUGIN_AUTO);
	// we will load the transformed file
	LPCTSTR pszFileName = sFileName.c_str();

	String sExt;
	DWORD nRetVal = FileLoadResult::FRESULT_OK;

	// Set encoding based on extension, if we know one
	SplitFilename(pszFileName, NULL, NULL, &sExt);
	CCrystalTextView::TextDefinition *def = 
		CCrystalTextView::GetTextType(sExt.c_str());
	if (def && def->encoding != -1)
		m_nSourceEncoding = def->encoding;
	
	UniFile *pufile = infoUnpacker->pufile;
	if (pufile == 0)
		pufile = new UniMemFile;

	// Now we only use the UniFile interface
	// which is something we could implement for HTTP and/or FTP files

	if (!pufile->OpenReadOnly(pszFileName))
	{
		nRetVal = FileLoadResult::FRESULT_ERROR;
		UniFile::UniError uniErr = pufile->GetLastUniError();
		if (uniErr.hasError())
		{
			if (uniErr.apiname.IsEmpty())
				sError = uniErr.desc;
			else
				sError = GetSysError(uniErr.syserrnum);
		}
		InitNew(); // leave crystal editor in valid, empty state
		goto LoadFromFileExit;
	}
	else
	{
		if (infoUnpacker->pluginName[0] != '\0')
		{
			// re-detect codepage
			FileTextEncoding encoding2;
			int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
			GuessCodepageEncoding(pszFileName, &encoding2,
				iGuessEncodingType);
			pufile->SetUnicoding(encoding2.m_unicoding);
			pufile->SetCodepage(encoding2.m_codepage);
			pufile->SetBom(encoding2.m_bom);
		}
		else
		{
			// If file is unicode(including UTF-8 without BOM), try to read BOM 
			// or else, use the codepage we were given to interpret the 8-bit characters
			if (encoding.m_unicoding == ucr::NONE  || !pufile->ReadBom())
				pufile->SetCodepage(encoding.m_codepage);
		}
		UINT lineno = 0;
		CString eol, preveol;
		CString sline;
		bool done = false;
		UINT next_line_report = 100; // for trace messages
		UINT next_line_multiple = 5; // for trace messages
		COleDateTime start = COleDateTime::GetCurrentTime(); // for trace messages

		// Manually grow line array exponentially
		UINT arraysize = 500;
		m_aLines.SetSize(arraysize);
		
		// preveol must be initialized for empty files
		preveol = "\n";
		
		do {
			bool lossy=false;
			done = !pufile->ReadString(sline, eol, &lossy);

			// if last line had no eol, we can quit
			if (done && preveol.IsEmpty())
				break;
			// but if last line had eol, we add an extra (empty) line to buffer

			// Manually grow line array exponentially
			if (lineno == arraysize)
			{
				arraysize *= 2;
				m_aLines.SetSize(arraysize);
			}

			sline += eol; // TODO: opportunity for optimization, as CString append is terrible
			if (lossy)
			{
				// TODO: Should record lossy status of line
			}
			AppendLine(lineno, sline, sline.GetLength());
			++lineno;
			preveol = eol;

#ifdef _DEBUG
			// send occasional line counts to trace
			// (at 100, 500, 1000, 5000, etc)
			if (lineno == next_line_report)
			{
				__int64 dwBytesRead = pufile->GetPosition();
				COleDateTimeSpan duration = COleDateTime::GetCurrentTime() - start;
				if (duration.GetTotalMinutes() > 0)
				{
					CString strace = GetLineByteTimeReport(lineno, dwBytesRead, start);
					TRACE(_T("%s\n"), (LPCTSTR)strace);
				}
				next_line_report = next_line_multiple * next_line_report;
				next_line_multiple = (next_line_multiple == 5) ? 2 : 5;
			}
#endif // _DEBUG
		} while (!done);

#ifdef _DEBUG
		// Send report of duration to trace (if it took a while)
		COleDateTime end = COleDateTime::GetCurrentTime();
		COleDateTimeSpan duration = end - start;
		if (duration.GetTotalMinutes() > 0)
		{
			__int64 dwBytesRead = pufile->GetPosition();
			CString strace = GetLineByteTimeReport(lineno, dwBytesRead, start);
			TRACE(_T("%s\n"), (LPCTSTR)strace);
		}
#endif // _DEBUG

		// fix array size (due to our manual exponential growth
		m_aLines.SetSize(lineno);
	
		
		//Try to determine current CRLF mode (most frequent)
		if (nCrlfStyle == CRLF_STYLE_AUTOMATIC)
		{
			nCrlfStyle = GetTextFileStyle(pufile->GetTxtStats());
		}
		ASSERT(nCrlfStyle >= 0 && nCrlfStyle <= 2);
		SetCRLFMode(nCrlfStyle);
		
		//  At least one empty line must present
		// (view does not work for empty buffers)
		ASSERT(m_aLines.GetSize() > 0);
		
		m_bInit = TRUE;
		m_bModified = FALSE;
		m_bUndoGroup = m_bUndoBeginGroup = FALSE;
		m_nUndoBufSize = 102400; // crystaltextbuffer.cpp - UNDO_BUF_SIZE;
		m_nSyncPosition = m_nUndoPosition = 0;
		ASSERT(m_aUndoBuf.GetSize() == 0);
		m_ptLastChange.x = m_ptLastChange.y = -1;
		
		FinishLoading();
		// flags don't need initialization because 0 is the default value

		// Set the return value : OK + info if the file is impure
		// A pure file is a file where EOL are consistent (all DOS, or all UNIX, or all MAC)
		// An impure file is a file with several EOL types
		// WinMerge may display impure files, but the default option is to unify the EOL
		// We return this info to the caller, so it may display a confirmation box
		if (IsTextFileStylePure(pufile->GetTxtStats()))
			nRetVal = FileLoadResult::FRESULT_OK;
		else
			nRetVal = FileLoadResult::FRESULT_OK_IMPURE;

		// stash original encoding away
		m_encoding.m_unicoding = pufile->GetUnicoding();
		m_encoding.m_bom = pufile->HasBom();
		m_encoding.m_codepage = pufile->GetCodepage();

		if (pufile->GetTxtStats().nlosses)
		{
			FileLoadResult::AddModifier(nRetVal, FileLoadResult::FRESULT_LOSSY);
			readOnly = TRUE;
		}
	}
	
LoadFromFileExit:
	// close the file now to free the handle
	pufile->Close();
	delete pufile;

	// delete the file that unpacking may have created
	if (_tcscmp(pszFileNameInit, pszFileName) != 0)
		if (!::DeleteFile(pszFileName))
		{
			LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
				pszFileName, GetSysError(GetLastError())));
		}

	return nRetVal;
}

/**
 * @brief Saves file from buffer to disk
 *
 * @param bTempFile : FALSE if we are saving user files and
 * TRUE if we are saving workin-temp-files for diff-engine
 *
 * @return SAVE_DONE or an error code (list in MergeDoc.h)
 */
int CMergeDoc::CDiffTextBuffer::SaveToFile (LPCTSTR pszFileName,
		BOOL bTempFile, CString & sError, PackingInfo * infoUnpacker /*= NULL*/,
		int nCrlfStyle /*= CRLF_STYLE_AUTOMATIC*/,
		BOOL bClearModifiedFlag /*= TRUE*/,
		BOOL bForceUTF8 /*= FALSE*/)
{
	ASSERT (nCrlfStyle == CRLF_STYLE_AUTOMATIC || nCrlfStyle == CRLF_STYLE_DOS ||
		nCrlfStyle == CRLF_STYLE_UNIX || nCrlfStyle == CRLF_STYLE_MAC);
	ASSERT (m_bInit);

	if (!pszFileName || _tcslen(pszFileName) == 0)
		return SAVE_FAILED;	// No filename, cannot save...

	if (nCrlfStyle == CRLF_STYLE_AUTOMATIC &&
		!GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
		infoUnpacker && infoUnpacker->disallowMixedEOL)
	{
			// get the default nCrlfStyle of the CDiffTextBuffer
		nCrlfStyle = GetCRLFMode();
		ASSERT(nCrlfStyle >= 0 && nCrlfStyle <= 2);
	}

	BOOL bOpenSuccess = TRUE;
	BOOL bSaveSuccess = FALSE;

	UniStdioFile file;
	file.SetUnicoding(bForceUTF8 ? ucr::UTF8 : m_encoding.m_unicoding);
	file.SetBom(bForceUTF8 ? true : m_encoding.m_bom);
	file.SetCodepage(bForceUTF8 ? CP_UTF8 : m_encoding.m_codepage);

	String sIntermediateFilename; // used when !bTempFile

	if (bTempFile)
	{
		bOpenSuccess = !!file.OpenCreate(pszFileName);
	}
	else
	{
		sIntermediateFilename = env_GetTempFileName(m_strTempPath.c_str(),
			_T("MRG_"), NULL);
		if (sIntermediateFilename.empty())
			return SAVE_FAILED;  //Nothing to do if even tempfile name fails
		bOpenSuccess = !!file.OpenCreate(sIntermediateFilename.c_str());
	}

	if (!bOpenSuccess)
	{	
		UniFile::UniError uniErr = file.GetLastUniError();
		if (uniErr.hasError())
		{
			if (uniErr.apiname.IsEmpty())
				sError = uniErr.desc;
			else
				sError = GetSysError(uniErr.syserrnum);
			if (bTempFile)
				LogErrorString(Fmt(_T("Opening file %s failed: %s"),
					pszFileName, sError));
			else
				LogErrorString(Fmt(_T("Opening file %s failed: %s"),
					sIntermediateFilename, sError));
		}
		return SAVE_FAILED;
	}

	file.WriteBom();

	// line loop : get each real line and write it in the file
	CString sLine;
	CString sEol = GetStringEol(nCrlfStyle);
	int nLineCount = m_aLines.GetSize();
	for (int line=0; line<nLineCount; ++line)
	{
		if (GetLineFlags(line) & LF_GHOST)
			continue;

		// get the characters of the line (excluding EOL)
		if (GetLineLength(line) > 0)
		{
			int nLineLength = GetLineLength(line);
			void *pszBuf = sLine.GetBuffer(nLineLength);
			memcpy(pszBuf, GetLineChars(line), nLineLength * sizeof(TCHAR));
			sLine.ReleaseBuffer(nLineLength);
		}
		else
			sLine = _T("");

		if (bTempFile)
			EscapeControlChars(sLine);
		// last real line ?
		int lastRealLine = ApparentLastRealLine();
		if (line == lastRealLine || lastRealLine == -1 )
		{
			// last real line is never EOL terminated
			ASSERT (_tcslen(GetLineEol(line)) == 0);
			// write the line and exit loop
			file.WriteString(sLine);
			break;
		}

		// normal real line : append an EOL
		if (nCrlfStyle == CRLF_STYLE_AUTOMATIC)
		{
			// either the EOL of the line (when preserve original EOL chars is on)
			sLine += GetLineEol(line);
		}
		else
		{
			// or the default EOL for this file
			sLine += sEol;
		}

		// write this line to the file (codeset or unicode conversions are done there)
		file.WriteString(sLine);
	}
	file.Close();


	if (!bTempFile)
	{
		// If we are saving user files
		// we need an unpacker/packer, at least a "do nothing" one
		ASSERT(infoUnpacker != NULL);
		// repack the file here, overwrite the temporary file we did save in
		String csTempFileName = sIntermediateFilename;
		infoUnpacker->subcode = unpackerSubcode;
		if (!FileTransform_Packing(csTempFileName, *infoUnpacker))
		{
			if (!::DeleteFile(sIntermediateFilename.c_str()))
			{
				LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
					sIntermediateFilename.c_str(), GetSysError(GetLastError())));
			}
			// returns now, don't overwrite the original file
			return SAVE_PACK_FAILED;
		}
		// the temp filename may have changed during packing
		if (csTempFileName != sIntermediateFilename)
		{
			if (!::DeleteFile(sIntermediateFilename.c_str()))
			{
				LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
					sIntermediateFilename.c_str(), GetSysError(GetLastError())));
			}
			sIntermediateFilename = csTempFileName;
		}

		// Write tempfile over original file
		if (::CopyFile(sIntermediateFilename.c_str(), pszFileName, FALSE))
		{
			if (!::DeleteFile(sIntermediateFilename.c_str()))
			{
				LogErrorString(Fmt(_T("DeleteFile(%s) failed: %s"),
					sIntermediateFilename.c_str(), GetSysError(GetLastError())));
			}
			if (bClearModifiedFlag)
			{
				SetModified(FALSE);
				m_nSyncPosition = m_nUndoPosition;
			}
			bSaveSuccess = TRUE;

			// remember revision number on save
			m_dwRevisionNumberOnSave = m_dwCurrentRevisionNumber;

			// redraw line revision marks
			UpdateViews (NULL, NULL, UPDATE_FLAGSONLY);	
		}
		else
		{
			sError = GetSysError(GetLastError());
			LogErrorString(Fmt(_T("CopyFile(%s, %s) failed: %s"),
				sIntermediateFilename.c_str(), pszFileName, sError));
		}
	}
	else
	{
		if (bClearModifiedFlag)
		{
			SetModified(FALSE);
			m_nSyncPosition = m_nUndoPosition;
		}
		bSaveSuccess = TRUE;
	}

	if (bSaveSuccess)
		return SAVE_DONE;
	else
		return SAVE_FAILED;
}

/// Replace line (removing any eol, and only including one if in strText)
void CMergeDoc::CDiffTextBuffer::ReplaceFullLines(CDiffTextBuffer& dbuf, CDiffTextBuffer& sbuf, CCrystalTextView * pSource, int nLineBegin, int nLineEnd, int nAction /*=CE_ACTION_UNKNOWN*/)
{
	int endl,endc;
	CString strText;
	if (nLineBegin != nLineEnd || sbuf.GetLineLength(nLineEnd) > 0)
		sbuf.GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, sbuf.GetLineLength(nLineEnd), strText);
	strText += sbuf.GetLineEol(nLineEnd);

	if (nLineBegin != nLineEnd || dbuf.GetFullLineLength(nLineEnd) > 0)
	{
		int nLineEndSource = nLineEnd < dbuf.GetLineCount() ? nLineEnd : dbuf.GetLineCount();
		if (nLineEnd+1 < GetLineCount())
		{
			dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource + 1, 0, nAction);
			dbuf.InsertGhostLine(pSource, nLineBegin);
		}
		else
			dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource, dbuf.GetLineLength(nLineEndSource), nAction); 
	}

	if (int cchText = strText.GetLength())
		dbuf.InsertText(pSource, nLineBegin, 0, strText, cchText, endl,endc, nAction);

}

/**
 * @brief Take care of rescanning document.
 * 
 * Update view and restore cursor and scroll position after
 * rescanning document.
 * @param [in] bForced If TRUE rescan cannot be suppressed
 */
void CMergeDoc::FlushAndRescan(BOOL bForced /* =FALSE */)
{
	// Ignore suppressing when forced rescan
	if (!bForced)
		if (!m_bEnableRescan) return;

	WaitStatusCursor waitstatus(IDS_STATUS_RESCANNING);

	int nActiveViewIndexType = GetActiveMergeViewIndexType();

	// store cursors and hide caret
	int nBuffer;
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		m_pView[nBuffer]->PushCursors();
		m_pDetailView[nBuffer]->PushCursors();
	}
	if (nActiveViewIndexType >= MERGEVIEW_PANE0 && nActiveViewIndexType <= MERGEVIEW_PANE2)
		m_pView[nActiveViewIndexType]->HideCursor();

	BOOL bBinary = FALSE;
	IDENTLEVEL identical = IDENTLEVEL_NONE;
	int nRescanResult = Rescan(bBinary, identical, bForced);

	// restore cursors and caret
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		m_pView[nBuffer]->PopCursors();
		m_pDetailView[nBuffer]->PopCursors();
	}
	if (nActiveViewIndexType >= MERGEVIEW_PANE0 && nActiveViewIndexType <= MERGEVIEW_PANE2)
		m_pView[nActiveViewIndexType]->ShowCursor();

	// because of ghostlines, m_nTopLine may differ just after Rescan
	// scroll both views to the same top line
	CMergeEditView * fixedView = m_pView[0];
	if (nActiveViewIndexType >= MERGEVIEW_PANE0 && nActiveViewIndexType <= MERGEVIEW_PANE2)
		// only one view needs to scroll so do not scroll the active view
		fixedView = m_pView[nActiveViewIndexType];
	fixedView->UpdateSiblingScrollPos(FALSE);

	// make sure we see the cursor from the curent view
	if (nActiveViewIndexType >= MERGEVIEW_PANE0 && nActiveViewIndexType <= MERGEVIEW_PANE2)
		m_pView[nActiveViewIndexType]->EnsureVisible(m_pView[nActiveViewIndexType]->GetCursorPos());

	// scroll both diff views to the same top line
	CMergeDiffDetailView * fixedDetailView = m_pDetailView[0];
	if (nActiveViewIndexType >= MERGEVIEW_PANE0_DETAIL && nActiveViewIndexType <= MERGEVIEW_PANE2_DETAIL)
		// only one view needs to scroll so do not scroll the active view
		fixedDetailView = m_pDetailView[nActiveViewIndexType - MERGEVIEW_PANE0_DETAIL];
	fixedDetailView->UpdateSiblingScrollPos(FALSE);

	// Refresh display
	UpdateAllViews(NULL);

	// Show possible error after updating screen
	if (nRescanResult != RESCAN_SUPPRESSED)
		ShowRescanError(nRescanResult, identical);
}

/**
 * @brief Saves both files
 */
void CMergeDoc::OnFileSave() 
{
	// We will need to know if either of the originals actually changed
	// so we know whether to update the diff status
	BOOL bChangedOriginal = FALSE;

	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		if (m_ptBuf[nBuffer]->IsModified() && !m_ptBuf[nBuffer]->GetReadOnly())
		{
			// (why we don't use return value of DoSave)
			// DoSave will return TRUE if it wrote to something successfully
			// but we have to know if it overwrote the original file
			BOOL bSaveOriginal = FALSE;
			DoSave(m_filePaths[nBuffer].c_str(), bSaveOriginal, nBuffer );
			if (bSaveOriginal)
				bChangedOriginal = TRUE;
		}
	}

	// If either of the actual source files being compared was changed
	// we need to update status in the dir view
	if (bChangedOriginal)
	{
		// If DirDoc contains diffs
		if (m_pDirDoc && m_pDirDoc->HasDiffs())
		{
			for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
			{
				if (m_bEditAfterRescan[nBuffer])
				{
					FlushAndRescan(FALSE);
					break;
				}
			}

			BOOL bIdentical = !m_diffList.HasSignificantDiffs(); // True if status should be set to identical
			m_pDirDoc->UpdateChangedItem(m_filePaths, m_diffList.GetSignificantDiffs(),
					m_nTrivialDiffs, bIdentical);
		}
	}
}

void CMergeDoc::DoFileSave(int nBuffer)
{
	BOOL bSaveSuccess = FALSE;
	BOOL bModified = FALSE;

	if (m_ptBuf[nBuffer]->IsModified() && !m_ptBuf[nBuffer]->GetReadOnly())
	{
		bModified = TRUE;
		DoSave(m_filePaths[nBuffer].c_str(), bSaveSuccess, nBuffer );
	}

	// If file were modified and saving succeeded,
	// update status on dir view
	if (bModified && bSaveSuccess)
	{
		// If DirDoc contains compare results
		if (m_pDirDoc && m_pDirDoc->HasDiffs())
		{
			for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
			{
				if (m_bEditAfterRescan[nBuffer])
				{
					FlushAndRescan(FALSE);
					break;
				}
			}

			BOOL bIdentical = !m_diffList.HasSignificantDiffs(); // True if status should be set to identical
			m_pDirDoc->UpdateChangedItem(m_filePaths, m_diffList.GetSignificantDiffs(),
					m_nTrivialDiffs, bIdentical);
		}
	}
}

/**
 * @brief Saves left-side file
 */
void CMergeDoc::OnFileSaveLeft()
{
	DoFileSave(0);
}

/**
 * @brief Saves middle-side file
 */
void CMergeDoc::OnFileSaveMiddle()
{
	DoFileSave(1);
}

/**
 * @brief Saves right-side file
 */
void CMergeDoc::OnFileSaveRight()
{
	DoFileSave(m_nBuffers - 1);
}

/**
 * @brief Saves left-side file with name asked
 */
void CMergeDoc::OnFileSaveAsLeft()
{
	BOOL bSaveResult = FALSE;
	DoSaveAs(m_filePaths.GetLeft().c_str(), bSaveResult, 0);
}

/**
 * @brief Called when "Save middle (as...)" item is updated
 */
void CMergeDoc::OnUpdateFileSaveAsMiddle(CCmdUI* pCmdUI)
{
	if (m_nBuffers == 3)
	{
		pCmdUI->Enable(TRUE);
	}
	else
	{
		pCmdUI->Enable(FALSE);
	}
}

/**
 * @brief Saves right-side file with name asked
 */
void CMergeDoc::OnFileSaveAsMiddle()
{
	BOOL bSaveResult = FALSE;
	DoSaveAs(m_filePaths.GetMiddle().c_str(), bSaveResult, 1);
}

/**
 * @brief Saves right-side file with name asked
 */
void CMergeDoc::OnFileSaveAsRight()
{
	BOOL bSaveResult = FALSE;
	DoSaveAs(m_filePaths.GetRight().c_str(), bSaveResult, m_nBuffers - 1);
}

/**
 * @brief Update diff-number pane text
 */
void CMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI) 
{
	TCHAR sIdx[32];
	TCHAR sCnt[32];
	String s;
	const int nDiffs = m_diffList.GetSignificantDiffs();
	
	// Files are identical - show text "Identical"
	if (nDiffs <= 0)
		s = theApp.LoadString(IDS_IDENTICAL);
	
	// There are differences, but no selected diff
	// - show amount of diffs
	else if (GetCurrentDiff() < 0)
	{
		s = theApp.LoadString(nDiffs == 1 ? IDS_1_DIFF_FOUND : IDS_NO_DIFF_SEL_FMT);
		string_replace(s, _T("%1"), _itot(nDiffs, sCnt, 10));
	}
	
	// There are differences and diff selected
	// - show diff number and amount of diffs
	else
	{
		s = theApp.LoadString(IDS_DIFF_NUMBER_STATUS_FMT);
		string_replace(s, _T("%1"), _itot(GetCurrentDiff() + 1, sIdx, 10));
		string_replace(s, _T("%2"), _itot(nDiffs, sCnt, 10));
	}
	pCmdUI->SetText(s.c_str());
}


/**
 * @brief Change number of diff context lines
 */
void CMergeDoc::OnDiffContext(UINT nID)
{
	switch (nID)
	{
	case ID_VIEW_DIFFCONTEXT_0:
		m_nDiffContext = 0; break;
	case ID_VIEW_DIFFCONTEXT_1:
		m_nDiffContext = 1; break;
	case ID_VIEW_DIFFCONTEXT_3:
		m_nDiffContext = 3; break;
	case ID_VIEW_DIFFCONTEXT_5:
		m_nDiffContext = 5; break;
	case ID_VIEW_DIFFCONTEXT_7:
		m_nDiffContext = 7; break;
	case ID_VIEW_DIFFCONTEXT_9:
		m_nDiffContext = 9; break;
	default:
		m_nDiffContext = -1; break;
	}
	GetOptionsMgr()->SaveOption(OPT_DIFF_CONTEXT, m_nDiffContext);
	FlushAndRescan(TRUE);
}

/**
 * @brief Update number of diff context lines
 */
void CMergeDoc::OnUpdateDiffContext(CCmdUI* pCmdUI)
{
	BOOL bCheck;
	switch (pCmdUI->m_nID)
	{
	case ID_VIEW_DIFFCONTEXT_0:
		bCheck = (m_nDiffContext == 0); break;
	case ID_VIEW_DIFFCONTEXT_1:
		bCheck = (m_nDiffContext == 1); break;
	case ID_VIEW_DIFFCONTEXT_3:
		bCheck = (m_nDiffContext == 3); break;
	case ID_VIEW_DIFFCONTEXT_5:
		bCheck = (m_nDiffContext == 5); break;
	case ID_VIEW_DIFFCONTEXT_7:
		bCheck = (m_nDiffContext == 7); break;
	case ID_VIEW_DIFFCONTEXT_9:
		bCheck = (m_nDiffContext == 9); break;
	default:
		bCheck = (m_nDiffContext < 0); break;
	}
	pCmdUI->SetCheck(bCheck);
	pCmdUI->Enable(TRUE);
}

bool CMergeDoc::CDiffTextBuffer::curUndoGroup()
{
	return (m_aUndoBuf.GetSize()!=0 && m_aUndoBuf[0].m_dwFlags&UNDO_BEGINGROUP);
}


/**
 * @brief Build the diff array and prepare buffers accordingly (insert ghost lines, set WinMerge flags)
 *
 * @note Buffers may have different length after PrimeTextBuffers. Indeed, no
 * synchronization is needed after the last line. So no ghost line will be created
 * to face an ignored difference in the last line (typically : 'ignore blank lines' 
 * + empty last line on one side).
 * If you fell that different length buffers are really strange, CHANGE FIRST
 * the last diff to take into account the empty last line.
 */
void CMergeDoc::PrimeTextBuffers()
{
	SetCurrentDiff(-1);
	m_nTrivialDiffs = 0;
	int nDiff;
	int nDiffCount = m_diffList.GetSize();
	int file;

	// walk the diff list and calculate numbers of extra lines to add
	int extras[3]={0};   // extra lines added to each view
	m_diffList.GetExtraLinesCounts(m_nBuffers, extras);

	// resize m_aLines once for each view
	UINT lcount[3];
	UINT lcountnew[3];
	
	for (file = 0; file < m_nBuffers; file++)
	{
		lcount[file] = m_ptBuf[file]->GetLineCount();
		lcountnew[file] = lcount[file] + extras[file];
		m_ptBuf[file]->m_aLines.SetSize(lcountnew[file]);
	}
// this ASSERT may be false because of empty last line (see function's note)
//	ASSERT(lcount0new == lcount1new);

	// walk the diff list backward, move existing lines to proper place,
	// add ghost lines, and set flags
	for (nDiff = nDiffCount - 1; nDiff >= 0; nDiff --)
	{
		DIFFRANGE curDiff;
		VERIFY(m_diffList.GetDiff(nDiff, curDiff));

		// move matched lines after curDiff
		int nline[3];
		for (file = 0; file < m_nBuffers; file++)
			nline[file] = lcount[file] - curDiff.end[file] - 1; // #lines on left/middle/right after current diff
		// Matched lines should really match...
		// But matched lines after last diff may differ because of empty last line (see function's note)
		if (nDiff < nDiffCount - 1)
			ASSERT(nline[0] == nline[1]);

		int nmaxline = 0;
		for (file = 0; file < m_nBuffers; file++)
		{
			// Move all lines after current diff down as far as needed
			// for any ghost lines we're about to insert
			m_ptBuf[file]->MoveLine(curDiff.end[file]+1, lcount[file]-1, lcountnew[file]-nline[file]);
			lcountnew[file] -= nline[file];
			lcount[file] -= nline[file];
			// move unmatched lines and add ghost lines
			nline[file] = curDiff.end[file] - curDiff.begin[file] + 1; // #lines in diff on left/middle/right
			nmaxline = max(nmaxline, nline[file]);
		}

		for (file = 0; file < m_nBuffers; file++)
		{
			m_ptBuf[file]->MoveLine(curDiff.begin[file], curDiff.end[file], lcountnew[file]-nmaxline);
			int nextra = nmaxline - nline[file];
			if (nextra > 0)
			{
				m_ptBuf[file]->SetEmptyLine(lcountnew[file] - nextra, nextra);
				DWORD dflag = LF_GHOST;
				if ((file == 0 && curDiff.op == OP_3RDONLY) || (file == 2 && curDiff.op == OP_1STONLY))
					dflag |= LF_SNP;
				for (int i = 1; i <= nextra; i++)
					m_ptBuf[file]->SetLineFlag(lcountnew[file]-i, dflag, TRUE, FALSE, FALSE);
			}
			lcountnew[file] -= nmaxline;

			lcount[file] -= nline[file];

			// set dbegin, dend, blank, and line flags
			curDiff.dbegin[file] = lcountnew[file];
		}

		switch (curDiff.op)
		{
		case OP_TRIVIAL:
			++m_nTrivialDiffs;
			// fall through and handle as diff
		case OP_DIFF:
		case OP_1STONLY:
		case OP_2NDONLY:
		case OP_3RDONLY:
			// set curdiff
			{
				for (file = 0; file < m_nBuffers; file++)
				{
					curDiff.dend[file] = lcountnew[file]+nmaxline-1;
					curDiff.blank[file] = -1;
					int nextra = nmaxline - nline[file];
					if (nmaxline > nline[file])
					{
						// more lines on left, ghost lines on right side
						curDiff.blank[file] = curDiff.dend[file]+1 - nextra;
					}
				}
			}
			// flag lines
			{
				for (file = 0; file < m_nBuffers; file++)
				{
					// left side
					UINT i;
					for (i = curDiff.dbegin[file]; i <= curDiff.dend[file] ; i++)
					{
						if (curDiff.blank[file] == -1 || (int)i < curDiff.blank[file])
						{
							// set diff or trivial flag
							DWORD dflag = (curDiff.op == OP_TRIVIAL) ? LF_TRIVIAL : LF_DIFF;
							if ((file == 0 && curDiff.op == OP_3RDONLY) || (file == 2 && curDiff.op == OP_1STONLY))
								dflag |= LF_SNP;
							m_ptBuf[file]->SetLineFlag(i, dflag, TRUE, FALSE, FALSE);
m_ptBuf[file]->SetLineFlag(i, LF_INVISIBLE, FALSE, FALSE, FALSE);
						}
						else
						{
							// ghost lines are already inserted (and flagged)
							// ghost lines opposite to trivial lines are ghost and trivial
							if (curDiff.op == OP_TRIVIAL)
								m_ptBuf[file]->SetLineFlag(i, LF_TRIVIAL, TRUE, FALSE, FALSE);
						}
					}
				}
			}
			break;
		}           // switch (curDiff.op)
		VERIFY(m_diffList.SetDiff(nDiff, curDiff));
	}             // for (nDiff = nDiffCount; nDiff-- > 0; )

	m_diffList.ConstructSignificantChain();

	// Used to strip trivial diffs out of the diff chain
	// if m_nTrivialDiffs
	// via copying them all to a new chain, then copying only non-trivials back
	// but now we keep all diffs, including trivial diffs


	for (file = 0; file < m_nBuffers; file++)
		m_ptBuf[file]->FinishLoading();
}

/**
 * @brief Checks if file has changed since last update (save or rescan).
 * @param [in] szPath File to check
 * @param [in] dfi Previous fileinfo of file
 * @param [in] bSave If TRUE Compare to last save-info, else to rescan-info
 * @param [in] nBuffer Index (0-based) of buffer
 * @return TRUE if file is changed.
 */
BOOL CMergeDoc::IsFileChangedOnDisk(LPCTSTR szPath, DiffFileInfo &dfi,
	BOOL bSave, int nBuffer)
{
	DiffFileInfo *fileInfo = NULL;
	BOOL bFileChanged = FALSE;
	BOOL bIgnoreSmallDiff = GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME);
	int tolerance = 0;
	if (bIgnoreSmallDiff)
		tolerance = SmallTimeDiff; // From MainFrm.h

	if (bSave)
		fileInfo = m_pSaveFileInfo[nBuffer];
	else
		fileInfo = m_pRescanFileInfo[nBuffer];

	dfi.Update(szPath);

	__int64 timeDiff = dfi.mtime - fileInfo->mtime;
	timeDiff = _abs64(timeDiff);
	if ((timeDiff > tolerance) || (dfi.size != fileInfo->size))
	{
		bFileChanged = TRUE;
	}

	return bFileChanged;
}

void CMergeDoc::HideLines()
{
	int nLine;
	int file;

	if (m_nDiffContext == -1)
	{
		for (file = 0; file < m_nBuffers; file++)
			m_pView[file]->SetEnableHideLines(FALSE);
		return;
	}

	int nLineCount = 0x7fffffff;
	for (file = 0; file < m_nBuffers; file++)
	{
		if (nLineCount > m_ptBuf[file]->GetLineCount())
			nLineCount = m_ptBuf[file]->GetLineCount();
	}

	for (nLine =  0; nLine < nLineCount;)
	{
		if (!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST)))
		{
			for (file = 0; file < m_nBuffers; file++)
				m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, TRUE, FALSE, FALSE);
			nLine++;
		}
		else
		{
			int nLine2 = (nLine - m_nDiffContext < 0) ? 0 : (nLine - m_nDiffContext);
			for (; nLine2 < nLine; nLine2++)
			{
				for (file = 0; file < m_nBuffers; file++)
					m_ptBuf[file]->SetLineFlag(nLine2, LF_INVISIBLE, FALSE, FALSE, FALSE);
			}
		
			for (; nLine < nLineCount; nLine++)
			{
				if (!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST)))
					break;
				for (file = 0; file < m_nBuffers; file++)
					m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, FALSE, FALSE, FALSE);
			}

			int nLineEnd2 = (nLine + m_nDiffContext >= nLineCount) ? nLineCount-1 : (nLine + m_nDiffContext);
			for (; nLine < nLineEnd2; nLine++)
			{
				for (file = 0; file < m_nBuffers; file++)
					m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, FALSE, FALSE, FALSE);
				if (m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST))
					nLineEnd2 = (nLine + 1 + m_nDiffContext >= nLineCount) ? nLineCount-1 : (nLine + 1 + m_nDiffContext);
			}
		}
	}

	for (file = 0; file < m_nBuffers; file++)
		m_pView[file]->SetEnableHideLines(TRUE);
}

/**
 * @brief Asks and then saves modified files.
 *
 * This function saves modified files. Dialog is shown for user to select
 * modified file(s) one wants to save or discard changed. Cancelling of
 * save operation is allowed unless denied by parameter. After successfully
 * save operation file statuses are updated to directory compare.
 * @param [in] bAllowCancel If FALSE "Cancel" button is disabled.
 * @return TRUE if user selected "OK" so next operation can be
 * executed. If FALSE user choosed "Cancel".
 * @note If filename is empty, we assume scratchpads are saved,
 * so instead of filename, description is shown.
 * @todo If we have filename and description for file, what should
 * we do after saving to different filename? Empty description?
 * @todo Parameter @p bAllowCancel is always true in callers - can be removed.
 */
BOOL CMergeDoc::PromptAndSaveIfNeeded(BOOL bAllowCancel)
{
	BOOL bLModified = FALSE, bMModified = FALSE, bRModified = FALSE;
	BOOL result = TRUE;
	BOOL bLSaveSuccess = FALSE, bMSaveSuccess = FALSE, bRSaveSuccess = FALSE;

	if (m_nBuffers == 3)
	{
		bLModified = m_ptBuf[0]->IsModified();
		bMModified = m_ptBuf[1]->IsModified();
		bRModified = m_ptBuf[2]->IsModified();
	}
	else
	{
		bLModified = m_ptBuf[0]->IsModified();
		bRModified = m_ptBuf[1]->IsModified();
	}
	if (!bLModified && !bMModified && !bRModified)
		 return TRUE;

	SaveClosingDlg dlg;
	dlg.DoAskFor(bLModified, bMModified, bRModified);
	if (!bAllowCancel)
		dlg.m_bDisableCancel = TRUE;
	if (!m_filePaths.GetLeft().empty())
		dlg.m_sLeftFile = m_filePaths.GetLeft().c_str();
	else
		dlg.m_sLeftFile = m_strDesc[0].c_str();
	if (m_nBuffers == 3)
	{
		if (!m_filePaths.GetMiddle().empty())
			dlg.m_sMiddleFile = m_filePaths.GetMiddle().c_str();
		else
			dlg.m_sMiddleFile = m_strDesc[1].c_str();
	}
	if (!m_filePaths.GetRight().empty())
		dlg.m_sRightFile = m_filePaths.GetRight().c_str();
	else
		dlg.m_sRightFile = m_strDesc[m_nBuffers - 1].c_str();

	if (dlg.DoModal() == IDOK)
	{
		if (bLModified && dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
		{
			if (!DoSave(m_filePaths.GetLeft().c_str(), bLSaveSuccess, 0))
				result = FALSE;
		}

		if (bMModified && dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
		{
			if (!DoSave(m_filePaths.GetMiddle().c_str(), bMSaveSuccess, 1))
				result = FALSE;
		}

		if (bRModified && dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
		{
			if (!DoSave(m_filePaths.GetRight().c_str(), bRSaveSuccess, m_nBuffers - 1))
				result = FALSE;
		}
	}
	else
	{	
		result = FALSE;
	}

	// If file were modified and saving was successfull,
	// update status on dir view
	if ((bLModified && bLSaveSuccess) || 
	     (bMModified && bMSaveSuccess) ||
		 (bRModified && bRSaveSuccess))
	{
		// If directory compare has results
		if (m_pDirDoc && m_pDirDoc->HasDiffs())
		{
			if (m_bEditAfterRescan[0] || m_bEditAfterRescan[1] || (m_nBuffers == 3 && m_bEditAfterRescan[2]))
				FlushAndRescan(FALSE);

			BOOL bIdentical = !m_diffList.HasSignificantDiffs(); // True if status should be set to identical
			m_pDirDoc->UpdateChangedItem(m_filePaths, m_diffList.GetSignificantDiffs(),
					m_nTrivialDiffs, bIdentical);
		}
	}

	return result;
}

/** Rescan only if we did not Rescan during the last timeOutInSecond seconds*/
void CMergeDoc::RescanIfNeeded(float timeOutInSecond)
{
	// if we did not rescan during the request timeOut, Rescan
	// else we did Rescan after the request, so do nothing
	COleDateTimeSpan elapsed = COleDateTime::GetCurrentTime() - m_LastRescan;
	if (elapsed.GetTotalSeconds() >= timeOutInSecond)
		// (laoran 08-01-2003) maybe should be FlushAndRescan(TRUE) ??
		FlushAndRescan();
}

/**
 * @brief We have two child views (left & right), so we keep pointers directly
 * at them (the MFC view list doesn't have them both)
 */
void CMergeDoc::SetMergeViews(CMergeEditView *pView[])
{
	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
		m_pView[nBuffer] = pView[nBuffer];
	}
}

/**
 * @brief Someone is giving us pointers to our detail views
 */
void CMergeDoc::SetMergeDetailViews(CMergeDiffDetailView * pDetailView[])
{
	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		ASSERT(pDetailView[nBuffer] && !m_pDetailView[nBuffer]);
		m_pDetailView[nBuffer] = pDetailView[nBuffer];
	}
}

/**
 * @brief DirDoc gives us its identity just after it creates us
 */
void CMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
{
	ASSERT(pDirDoc && !m_pDirDoc);
	m_pDirDoc = pDirDoc;
}

/**
 * @brief Return pointer to parent frame
 */
CChildFrame * CMergeDoc::GetParentFrame() 
{
	return dynamic_cast<CChildFrame *>(m_pView[0]->GetParentFrame()); 
}

/**
 * @brief DirDoc is closing
 */
void CMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
{
	ASSERT(m_pDirDoc == pDirDoc);
	m_pDirDoc = 0;
	// TODO (Perry 2003-03-30): perhaps merge doc should close now ?
}

/**
 * @brief DirDoc commanding us to close
 */
BOOL CMergeDoc::CloseNow()
{
	// Allow user to cancel closing
	if (!PromptAndSaveIfNeeded(TRUE))
		return FALSE;

	GetParentFrame()->CloseNow();
	return TRUE;
}

/**
 * @brief Loads file to buffer and shows load-errors
 * @param [in] sFileName File to open
 * @param [in] nBuffer Index (0-based) of buffer to load
 * @param [out] readOnly whether file is read-only
 * @param [in] encoding encoding used
 * @return Tells if files were loaded successfully
 * @sa CMergeDoc::OpenDocs()
 **/
int CMergeDoc::LoadFile(CString sFileName, int nBuffer, BOOL & readOnly, const FileTextEncoding & encoding)
{
	CDiffTextBuffer *pBuf;
	CString sError;
	DWORD retVal = FileLoadResult::FRESULT_ERROR;

	pBuf = m_ptBuf[nBuffer];
	m_filePaths[nBuffer] = sFileName;

	int nCrlfStyle = CRLF_STYLE_AUTOMATIC;
	CString sOpenError;
	retVal = pBuf->LoadFromFile(sFileName, m_pInfoUnpacker,
		m_strBothFilenames.c_str(), readOnly, nCrlfStyle, encoding, sOpenError);

	// if CMergeDoc::CDiffTextBuffer::LoadFromFile failed,
	// it left the pBuf in a valid (but empty) state via a call to InitNew

	if (FileLoadResult::IsOkImpure(retVal))
	{
		// File loaded, and multiple EOL types in this file
		FileLoadResult::SetMainOk(retVal);

		// If mixed EOLs are not enabled, enable them for this doc.
		if (!GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL))
		{
			m_bMixedEol = TRUE;
		}
	}

	if (FileLoadResult::IsError(retVal))
	{
		// Error from Unifile/system
		if (!sOpenError.IsEmpty())
			LangFormatString2(sError, IDS_ERROR_FILEOPEN, sFileName, sOpenError);
		else
			LangFormatString1(sError, IDS_ERROR_FILE_NOT_FOUND, sFileName);
		AfxMessageBox(sError, MB_OK | MB_ICONSTOP | MB_MODELESS);
	}
	else if (FileLoadResult::IsErrorUnpack(retVal))
	{
		LangFormatString1(sError, IDS_ERROR_FILE_NOT_UNPACKED, sFileName);
		AfxMessageBox(sError, MB_OK | MB_ICONSTOP | MB_MODELESS);
	}
	return retVal;
}

/**
 * @brief Check if specified codepage number is valid for WinMerge Editor.
 * @param [in] cp Codepage number to check.
 * @return true if codepage is valid, false otherwise.
 */
bool CMergeDoc::IsValidCodepageForMergeEditor(unsigned cp) const
{
	if (!cp) // 0 is our signal value for invalid
		return false;
	// Codepage must be actually installed on system
	// for us to be able to use it
	// We accept whatever codepages that codepage module says are installed
	return true;/*isCodepageInstalled(cp);*/ /* FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME */
}

/**
 * @brief Sanity check file's specified codepage.
 * This function checks if file's specified codepage is valid for WinMerge
 * editor and if not resets the codepage to default.
 * @param [in,out] fileinfo Class containing file's codepage.
 */
void CMergeDoc::SanityCheckCodepage(FileLocation & fileinfo)
{
	if (fileinfo.encoding.m_unicoding == ucr::NONE
		&& !IsValidCodepageForMergeEditor(fileinfo.encoding.m_codepage))
	{
		int cp = getDefaultCodepage();
		if (!IsValidCodepageForMergeEditor(cp))
			cp = CP_ACP;
		fileinfo.encoding.SetCodepage(cp);
	}
}

/**
 * @brief Loads one file from disk and updates file infos.
 * @param [in] index Index of file in internal buffers.
 * @param [in] filename File's name.
 * @param [in] readOnly Is file read-only?
 * @param [in] encoding File's encoding.
 * @return One of FileLoadResult values.
 */
DWORD CMergeDoc::LoadOneFile(int index, String filename, BOOL readOnly,
		const FileTextEncoding & encoding)
{
	DWORD loadSuccess = FileLoadResult::FRESULT_ERROR;;
	
	if (!filename.empty())
	{
		if (GetMainFrame()->m_strDescriptions[index].empty())
			m_nBufferType[index] = BUFFER_NORMAL;
		else
		{
			m_nBufferType[index] = BUFFER_NORMAL_NAMED;
			m_strDesc[index] = GetMainFrame()->m_strDescriptions[index];
			GetMainFrame()->m_strDescriptions[index].erase();
		}
		m_pSaveFileInfo[index]->Update(filename);
		m_pRescanFileInfo[index]->Update(filename);

		loadSuccess = LoadFile(filename.c_str(), index, readOnly, encoding);
	}
	else
	{
		m_nBufferType[index] = BUFFER_UNNAMED;
		m_ptBuf[index]->InitNew();
		m_strDesc[index] = GetMainFrame()->m_strDescriptions[index];
		m_ptBuf[index]->m_encoding = encoding;
		loadSuccess = FileLoadResult::FRESULT_OK;
	}
	return loadSuccess;
}

/**
 * @brief Loads files and does initial rescan.
 * @param fileloc [in] File to open to left/middle/right side (path & encoding info)
 * @param bRO [in] Is left/middle/right file read-only
 * @return Success/Failure/Binary (failure) per typedef enum OpenDocsResult_TYPE
 * @todo Options are still read from CMainFrame, this will change
 * @sa CMainFrame::ShowMergeDoc()
 */
OPENRESULTS_TYPE CMergeDoc::OpenDocs(FileLocation fileloc[],
		BOOL bRO[])
{
	IDENTLEVEL identical = IDENTLEVEL_NONE;
	int nRescanResult = RESCAN_OK;
	int nBuffer;

	// Filter out invalid codepages, or editor will display all blank
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		SanityCheckCodepage(fileloc[nBuffer]);

	// clear undo stack
	undoTgt.clear();
	curUndo = undoTgt.begin();

	// Prevent displaying views during LoadFile
	// Note : attach buffer again only if both loads succeed
	m_strBothFilenames.erase();
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		m_pView[nBuffer]->DetachFromBuffer();
		m_pDetailView[nBuffer]->DetachFromBuffer();
		
		// clear undo buffers
		m_ptBuf[nBuffer]->m_aUndoBuf.RemoveAll();

		// free the buffers
		m_ptBuf[nBuffer]->FreeAll ();

		// build the text being filtered, "|" separates files as it is forbidden in filenames
		m_strBothFilenames += fileloc[nBuffer].filepath + _T("|");
	}
	m_strBothFilenames.erase(m_strBothFilenames.length() - 1);

	// Load files
	DWORD nSuccess[3];
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		nSuccess[nBuffer] = LoadOneFile(nBuffer, (LPCTSTR)fileloc[nBuffer].filepath.c_str(), bRO[nBuffer],
			fileloc[nBuffer].encoding);
	}
	// scratchpad : we don't call LoadFile, so
	// we need to initialize the unpacker as a "do nothing" one
	IF_IS_TRUE_ALL ((m_nBufferType[nBuffer] == BUFFER_UNNAMED), nBuffer, m_nBuffers)
	{
		m_pInfoUnpacker->Initialize(PLUGIN_MANUAL);
	}

	// Bail out if either side failed
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		if (!FileLoadResult::IsOk(nSuccess[nBuffer]))
			break;
	}
	if (nBuffer < m_nBuffers)
	{
		OPENRESULTS_TYPE retVal = OPENRESULTS_FAILED_MISC;
		CChildFrame *pFrame = GetParentFrame();
		if (pFrame)
		{
			// Use verify macro to trap possible error in debug.
			VERIFY(pFrame->DestroyWindow());
		}
		return retVal;
	}

	// Warn user if file load was lossy (bad encoding)
	int idres=0;
	int nLossyBuffers = 0;
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		if (FileLoadResult::IsLossy(nSuccess[nBuffer]))
		{
			// TODO: It would be nice to report how many lines were lossy
			// we did calculate those numbers when we loaded the files, in the text stats
	
			idres = IDS_LOSSY_TRANSCODING_FIRST + nBuffer;
			nLossyBuffers++;
		}
	}
	if (nLossyBuffers > 1)
		idres = IDS_LOSSY_TRANSCODING_BOTH; /* FIXEME */
	
	if (nLossyBuffers > 0)
		LangMessageBox(idres, MB_ICONSTOP | MB_MODELESS);

	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		// Now buffers data are valid
		m_pView[nBuffer]->AttachToBuffer();
		m_pDetailView[nBuffer]->AttachToBuffer();

		// Currently there is only one set of syntax colors, which all documents & views share
		m_pView[nBuffer]->SetColorContext(GetMainSyntaxColors());
		m_pDetailView[nBuffer]->SetColorContext(GetMainSyntaxColors());

		// Set read-only statuses
		m_ptBuf[nBuffer]->SetReadOnly(bRO[nBuffer]);
	}

	// Check the EOL sensitivity option (do it before Rescan)
	DIFFOPTIONS diffOptions = {0};
	m_diffWrapper.GetOptions(&diffOptions);
	if (!GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) && !diffOptions.bIgnoreEol)
	{
		for (nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
			if (m_ptBuf[0]->GetCRLFMode() != m_ptBuf[nBuffer]->GetCRLFMode())
				break;

		if (nBuffer < m_nBuffers)
		{
			// Options and files not are not compatible :
			// Sensitive to EOL on, allow mixing EOL off, and files have a different EOL style.
			// All lines will differ, that is not very interesting and probably not wanted.
			// Propose to turn off the option 'sensitive to EOL'
			String s = theApp.LoadString(IDS_SUGGEST_IGNOREEOL);
			if (AfxMessageBox(s.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_SUGGEST_IGNOREEOL) == IDYES)
			{
				diffOptions.bIgnoreEol = TRUE;
				m_diffWrapper.SetOptions(&diffOptions);
			}
		}
	}

	// Define the prediffer
	PackingInfo * infoUnpacker = 0;
	PrediffingInfo * infoPrediffer = 0;
	m_pDirDoc->FetchPluginInfos(m_strBothFilenames.c_str(), &infoUnpacker, &infoPrediffer);
	m_diffWrapper.SetPrediffer(infoPrediffer);
	m_diffWrapper.SetTextForAutomaticPrediff(m_strBothFilenames);

	BOOL bBinary = FALSE;
	nRescanResult = Rescan(bBinary, identical);

	// Open filed if rescan succeed and files are not binaries
	if (nRescanResult == RESCAN_OK)
	{
		// set the document types
		// Warning : it is the first thing to do (must be done before UpdateView,
		// or any function that calls UpdateView, like SelectDiff)
		// Note: If option enabled, and another side type is not recognized,
		// we use recognized type for unrecognized side too.
		String sext;
		BOOL bTyped[3];
		int paneTyped = 0;

		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			if (m_pInfoUnpacker->textType.length())
				sext = m_pInfoUnpacker->textType;
			else
				sext = GetFileExt(fileloc[nBuffer].filepath.c_str(), m_strDesc[nBuffer].c_str());
			bTyped[nBuffer] = GetView(nBuffer)->SetTextType(sext.c_str());
			GetDetailView(nBuffer)->SetTextType(sext.c_str());
			if (bTyped[nBuffer])
				paneTyped = nBuffer;
		}

		for (nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
		{
			if (bTyped[0] != bTyped[nBuffer])
				break;
		}
		if (nBuffer < m_nBuffers)
		{
			for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
			{
				CCrystalTextView::TextDefinition *enuType;
				if (!bTyped[nBuffer])
				{
					enuType = GetView(paneTyped)->GetTextType(sext.c_str());
					GetView(nBuffer)->SetTextType(enuType);
					GetDetailView(nBuffer)->SetTextType(enuType);
				}
			}
		}

		int nNormalBuffer = 0;
		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			// set the frame window header
			UpdateHeaderPath(nBuffer);

			GetView(nBuffer)->DocumentsLoaded();
			GetDetailView(nBuffer)->DocumentsLoaded();
			
			if ((m_nBufferType[nBuffer] == BUFFER_NORMAL) ||
			    (m_nBufferType[nBuffer] == BUFFER_NORMAL_NAMED))
			{
				nNormalBuffer++;
			}
			
		}

		// Inform user that files are identical
		// Don't show message if new buffers created
		if (identical == IDENTLEVEL_ALL && nNormalBuffer > 0)
		{
			ShowRescanError(nRescanResult, identical);
		}

		// scroll to first diff
		if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST) &&
			m_diffList.HasSignificantDiffs())
		{
			int nDiff = m_diffList.FirstSignificantDiff();
			m_pView[0]->SelectDiff(nDiff, TRUE, FALSE);
		}

		// Exit if files are identical should only work for the first
		// comparison and must be disabled afterward.
		GetMainFrame()->m_bExitIfNoDiff = FALSE;
	}
	else
	{
		// CMergeDoc::Rescan fails if files do not exist on both sides 
		// or the really arcane case that the temp files couldn't be created, 
		// which is too obscure to bother reporting if you can't write to 
		// your temp directory, doing nothing is graceful enough for that).
		ShowRescanError(nRescanResult, identical);
		GetParentFrame()->DestroyWindow();
		return OPENRESULTS_FAILED_MISC;
	}

	// Force repaint of location pane to update it in case we had some warning
	// dialog visible and it got painted before files were loaded
	if (m_pView[0])
		m_pView[0]->RepaintLocationPane();

	return OPENRESULTS_SUCCESS;
}

/**
 * @brief Refresh cached options.
 *
 * For compare speed, we have to cache some frequently needed options,
 * instead of getting option value every time from OptionsMgr. This
 * function must be called every time options are changed to OptionsMgr.
 */
void CMergeDoc::RefreshOptions()
{
	DIFFOPTIONS options = {0};
	
	m_diffWrapper.SetDetectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
	options.nIgnoreWhitespace = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
	options.bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
	options.bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
	options.bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
	options.bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);

	m_diffWrapper.SetOptions(&options);

	// Refresh view options
	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		m_pView[nBuffer]->RefreshOptions();
}

/**
 * @brief Write path and filename to headerbar
 * @note SetText() does not repaint unchanged text
 */
void CMergeDoc::UpdateHeaderPath(int pane)
{
	CChildFrame *pf = GetParentFrame();
	ASSERT(pf);
	String sText;
	BOOL bChanges = FALSE;

	if (m_nBufferType[pane] == BUFFER_UNNAMED ||
		m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
	{
		sText = m_strDesc[pane];
	}
	else
	{
		sText = m_filePaths[pane];
		if (m_pDirDoc)
		{
			m_pDirDoc->ApplyDisplayRoot(pane, sText);
		}
	}
	bChanges = m_ptBuf[pane]->IsModified();

	if (bChanges)
		sText.insert(0, _T("* "));

	pf->GetHeaderInterface()->SetText(pane, sText.c_str());

	SetTitle(NULL);
}

/**
 * @brief Paint differently the headerbar of the active view
 */
void CMergeDoc::UpdateHeaderActivity(int pane, BOOL bActivate)
{
	CChildFrame *pf = GetParentFrame();
	ASSERT(pf);
	pf->GetHeaderInterface()->SetActive(pane, bActivate);
}

/**
 * @brief Return if doc is in Merging/Editing mode
 */
BOOL CMergeDoc::GetMergingMode() const
{
	return m_bMergingMode;
}

/**
 * @brief Set doc to Merging/Editing mode
 */
void CMergeDoc::SetMergingMode(BOOL bMergingMode)
{
	m_bMergingMode = bMergingMode;
	GetOptionsMgr()->SaveOption(OPT_MERGE_MODE, m_bMergingMode == TRUE);
}

/**
 * @brief Set detect/not detect Moved Blocks
 */
void CMergeDoc::SetDetectMovedBlocks(BOOL bDetectMovedBlocks)
{
	if (bDetectMovedBlocks == m_diffWrapper.GetDetectMovedBlocks())
		return;

	GetOptionsMgr()->SaveOption(OPT_CMP_MOVED_BLOCKS, bDetectMovedBlocks == TRUE);
	m_diffWrapper.SetDetectMovedBlocks(bDetectMovedBlocks);
	FlushAndRescan();
}

void CMergeDoc::SetEditedAfterRescan(int nBuffer)
{
	m_bEditAfterRescan[nBuffer] = TRUE;
}

/**
 * @brief Update document filenames to title
 */
void CMergeDoc::SetTitle(LPCTSTR lpszTitle)
{
	const TCHAR pszSeparator[] = _T(" - ");
	String sTitle;
	String sFileName[3];

	if (lpszTitle)
		sTitle = lpszTitle;
	else
	{
		for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			if (!m_strDesc[nBuffer].empty())
				sFileName[nBuffer] = m_strDesc[nBuffer];
			else
			{
				String file;
				String ext;
				SplitFilename(m_filePaths[nBuffer].c_str(), NULL, &file, &ext);
				sFileName[nBuffer] += file.c_str();
				if (!ext.empty())
				{
					sFileName[nBuffer] += _T(".");
					sFileName[nBuffer] += ext.c_str();
				}
			}
		}
		if (m_nBuffers < 3)
		{
			if (sFileName[0] == sFileName[1])
			{
				sTitle = sFileName[0];
				sTitle += _T(" x 2");
			}
			else
			{
				sTitle = sFileName[0];
				sTitle += pszSeparator;
				sTitle += sFileName[1];
			}
		}
		else
		{
			if (sFileName[0] == sFileName[1] && sFileName[0] == sFileName[2])
			{
				sTitle = sFileName[0];
				sTitle += _T(" x 3");
			}
			else
			{
				sTitle = sFileName[0];
				sTitle += pszSeparator;
				sTitle += sFileName[1];
				sTitle += pszSeparator;
				sTitle += sFileName[2];
			}
		}

	}
	CDocument::SetTitle(sTitle.c_str());
}

/**
 * @brief Update any resources necessary after a GUI language change
 */
void CMergeDoc::UpdateResources()
{
	CString str;
	int nBuffer;

	m_strDesc[0] = theApp.LoadString(IDS_EMPTY_LEFT_FILE);
	m_strDesc[m_nBuffers - 1] = theApp.LoadString(IDS_EMPTY_RIGHT_FILE);
	if (m_nBuffers == 3)
		m_strDesc[1] = theApp.LoadString(IDS_EMPTY_MIDDLE_FILE);
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		UpdateHeaderPath(nBuffer);

	GetParentFrame()->UpdateResources();
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		GetView(nBuffer)->UpdateResources();
}

// Return current word breaking break type setting (whitespace only or include punctuation)
bool CMergeDoc::GetBreakType() const
{
	BOOL breakType = GetOptionsMgr()->GetInt(OPT_BREAK_TYPE);
	return breakType > 0;
}

// Return true to do line diff colors at the byte level (false to do them at word level)
bool CMergeDoc::GetByteColoringOption() const
{
	// color at byte level if 'break_on_words' option not set
	BOOL breakWords = GetOptionsMgr()->GetBool(OPT_BREAK_ON_WORDS);
	return !breakWords;
}

/// Swap files and update views
void CMergeDoc::SwapFiles()
{
	// Swap views
	int nLeftViewId = m_pView[0]->GetDlgCtrlID();
	int nRightViewId = m_pView[m_nBuffers - 1]->GetDlgCtrlID();
	m_pView[0]->SetDlgCtrlID(nRightViewId);
	m_pView[m_nBuffers - 1]->SetDlgCtrlID(nLeftViewId);

	int nLeftDetailViewId = m_pDetailView[0]->GetDlgCtrlID();
	int nRightDetailViewId = m_pDetailView[m_nBuffers - 1]->GetDlgCtrlID();
	m_pDetailView[0]->SetDlgCtrlID(nRightDetailViewId);
	m_pDetailView[m_nBuffers - 1]->SetDlgCtrlID(nLeftDetailViewId);

	// Swap buffers and so on
	swap<CDiffTextBuffer *>(&m_ptBuf[0], &m_ptBuf[m_nBuffers - 1]);
	swap<CMergeEditView *>(&m_pView[0], &m_pView[m_nBuffers - 1]);
	swap<CMergeDiffDetailView *>(&m_pDetailView[0], &m_pDetailView[m_nBuffers - 1]);
	swap<DiffFileInfo *>(&m_pSaveFileInfo[0], &m_pSaveFileInfo[m_nBuffers - 1]);
	swap<DiffFileInfo *>(&m_pRescanFileInfo[0], &m_pRescanFileInfo[m_nBuffers - 1]);
	swap<BUFFERTYPE>(&m_nBufferType[0], &m_nBufferType[m_nBuffers - 1]);
	swap<BOOL>(&m_bEditAfterRescan[0], &m_bEditAfterRescan[m_nBuffers - 1]);
	swap<String>(&m_strDesc[0], &m_strDesc[m_nBuffers - 1]);

	m_filePaths.Swap();
	
	m_diffList.Swap(0, m_nBuffers - 1);

	for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		m_ptBuf[nBuffer]->m_nThisPane = nBuffer;
		m_pView[nBuffer]->m_nThisPane = nBuffer;
		m_pDetailView[nBuffer]->m_nThisPane = nBuffer;

		// Update views
		UpdateHeaderPath(nBuffer);
	}
	GetParentFrame()->UpdateSplitter();

	UpdateAllViews(NULL);
}

/**
 * @brief Update "Reload" item
 */
void CMergeDoc::OnUpdateFileReload(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(TRUE);
}

/**
 * @brief Reloads the opened files
 */
void CMergeDoc::OnFileReload()
{
	if (!PromptAndSaveIfNeeded(TRUE))
		return;
	
	FileLocation fileloc[3];
	BOOL bRO[3];
	for (int pane = 0; pane < m_nBuffers; pane++)
	{
		bRO[pane] = m_ptBuf[pane]->GetReadOnly();
		fileloc[pane].encoding.m_unicoding = m_ptBuf[pane]->getUnicoding();
		fileloc[pane].encoding.m_codepage = m_ptBuf[pane]->getCodepage();
		fileloc[pane].setPath(m_filePaths[pane].c_str());
		GetMainFrame()->m_strDescriptions[pane] = m_strDesc[pane];
	}
	OpenDocs(fileloc, bRO);
}

/**
 * @brief Display encodings to user
 */
void CMergeDoc::OnFileEncoding()
{
	DoFileEncodingDialog();
}

/**
 * @brief Update "File Encoding" item
 */
void CMergeDoc::OnUpdateFileEncoding(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(TRUE);
}

// Return file extension either from file name or file description (if WinMerge is used as an
// external Rational ClearCase tool.
String CMergeDoc::GetFileExt(LPCTSTR sFileName, LPCTSTR sDescription)
{
	String sExt;
	SplitFilename(sFileName, NULL, NULL, &sExt);

	if (TRUE == GetMainFrame()->m_bClearCaseTool)
	{
		// If no extension found in real file name.
		if (sExt.empty())
		{
			SplitViewName(sFileName, NULL, NULL, &sExt);
		}
		// If no extension found in repository file name.
		if (TRUE == sExt.empty())
		{
			SplitViewName(sDescription, NULL, NULL, &sExt);
		}
	}
	return sExt;
}

/**
 * @brief Generate report from file compare results.
 */
void CMergeDoc::OnToolsGenerateReport()
{
	CString s, folder;

	if (!SelectFile(GetMainFrame()->GetSafeHwnd(), s, folder, IDS_SAVE_AS_TITLE, IDS_HTML_REPORT_FILES, FALSE, _T("htm")))
		return;

	// calculate HTML font size
	LOGFONT lf;
	CDC dc;
	dc.CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
	m_pView[0]->GetFont(lf);
	int nFontSize = -MulDiv (lf.lfHeight, 72, dc.GetDeviceCaps (LOGPIXELSY));

	// create HTML report
	UniStdioFile file;
	if (!file.Open(s, _T("wt")))
	{
		ResMsgBox1(IDS_REPORT_ERROR, GetSysError(GetLastError()), MB_OK | MB_ICONSTOP);
		return;
	}

	file.SetCodepage(CP_UTF8);

	file.WriteString(
		Fmt(
		_T("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n")
		_T("\t\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n")
		_T("<html>\n")
		_T("<head>\n")
		_T("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n")
		_T("<title>WinMerge File Compare Report</title>\n")
		_T("<style type=\"text/css\">\n")
		_T("<!--\n")
		_T("td,th {word-break: break-all; font-size: %dpt;}\n")
		_T(".ln {text-align: right; word-break: keep-all; background-color: lightgrey;}\n")
		_T(".title {color: white; background-color: blue; vertical-align: top;}\n")
		_T("%s")
		_T("-->\n")
		_T("</style>\n")
		_T("</head>\n")
		_T("<body>\n")
		_T("<table cellspacing=\"0\" cellpadding=\"0\" style=\"width: 100%%; margin: 0; border: none;\">\n")
		_T("<thead>\n")
		_T("<tr>\n")
		, nFontSize, m_pView[0]->GetHTMLStyles()));

	// Get paths
	// If archive, use archive path + folder + filename inside archive
	// If desc text given, use it
	PathContext paths = m_filePaths;
	if (m_pDirDoc->IsArchiveFolders())
	{
		for (int i = 0; i < paths.GetSize(); i++)
			m_pDirDoc->ApplyDisplayRoot(i, paths[i]);
	}
	else
	{
		for (int i = 0; i < paths.GetSize(); i++)
		{
			if (!m_strDesc[i].empty())
				paths[i] = m_strDesc[i];
		}
	}

	// left and right title
	int nBuffer;
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
	{
		int nLineNumberColumnWidth = m_pView[nBuffer]->GetViewLineNumbers() ? 1 : 0;
		file.WriteString(Fmt(_T("<th class=\"title\" style=\"width:%d%%\"></th>"), 
			nLineNumberColumnWidth));
		file.WriteString(Fmt(_T("<th class=\"title\" style=\"width:%f%%\">"),
			(double)(100 - nLineNumberColumnWidth * m_nBuffers) / m_nBuffers));
		file.WriteString(paths[nBuffer].c_str());
		file.WriteString(_T("</th>\n"));
	}
	file.WriteString(
		_T("</tr>\n")
		_T("</thead>\n")
		_T("<tbody>\n"));

	// write the body of the report
	int idx[3] = {0};
	int nLineCount[3] = {0};
	for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		nLineCount[nBuffer] = m_ptBuf[nBuffer]->GetLineCount();

	for (;;)
	{
		file.WriteString(_T("<tr valign=\"top\">\n"));
		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			for (; idx[nBuffer] < nLineCount[nBuffer]; idx[nBuffer]++)
			{
				if (m_pView[nBuffer]->GetLineVisible(idx[nBuffer]))
					break;
			}
				
			if (idx[nBuffer] < nLineCount[nBuffer])
			{
				// line number
				DWORD dwFlags = m_ptBuf[nBuffer]->GetLineFlags(idx[nBuffer]);
				if (!(dwFlags & LF_GHOST) && m_pView[nBuffer]->GetViewLineNumbers())
					file.WriteString(Fmt(_T("<td class=\"ln\">%d</td>"), m_ptBuf[nBuffer]->ComputeRealLine(idx[nBuffer]) + 1));
				else
					file.WriteString(_T("<td class=\"ln\"></td>"));
				// write a line on left/right side
				file.WriteString(m_pView[nBuffer]->GetHTMLLine(idx[nBuffer], _T("td")));
				idx[nBuffer]++;
			}
			else
				file.WriteString("<td class=\"ln\"></td><td></td>");
			file.WriteString(_T("\n"));
		}
		file.WriteString(_T("</tr>\n"));

		BOOL bBorderLine = FALSE;
		for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
		{
			if (idx[nBuffer] < nLineCount[nBuffer] && !m_pView[nBuffer]->GetLineVisible(idx[nBuffer]))
				bBorderLine = TRUE;
		}

		if (bBorderLine)
		{
			file.WriteString(_T("<tr height=1>"));
			for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
			{
				if (idx[nBuffer] < nLineCount[nBuffer] && !m_pView[nBuffer]->GetLineVisible(idx[nBuffer]))
					file.WriteString("<td style=\"background-color: black\"></td><td style=\"background-color: black\"></td>");
				else
					file.WriteString("<td></td><td></td>");
			}
			file.WriteString(_T("</tr>\n"));
		}

		if (idx[0] >= nLineCount[0] && idx[1] >= nLineCount[1] && (m_nBuffers < 3 || idx[2] >= nLineCount[2]))
			break;
	}
	file.WriteString(
		_T("</tbody>\n")
		_T("</table>\n")
		_T("</body>\n")
		_T("</html>\n"));

	file.Close();

	LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION);
}
