//
// Copyright 1997 Nicholas J. Irias.  All rights reserved.
//
//

// Cmd_Integrate.cpp

#include "stdafx.h"
#include "p4win.h"
#include "cmd_integrate.h"
#include "cmd_delete.h"

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


IMPLEMENT_DYNCREATE(CCmd_Integrate, CP4Command)


CCmd_Integrate::CCmd_Integrate(CGuiClient *client) : CP4Command(client)
{
	m_ReplyMsg= WM_P4INTEGRATE;
	m_TaskName= _T("Integrate");
	m_ClobberFailed = m_IsRename = m_DeleteChg = FALSE;
	m_ChangeNbr = 0;
}

CCmd_Integrate::~CCmd_Integrate()
{
	Error e;
	if (m_DeleteChg || (m_FatalError && m_IsRename && m_ChangeNbr))
	{
		CString txt;
		txt.Format(_T("%ld"), m_ChangeNbr);
		CCmd_Delete cmd1(m_pClient);
		cmd1.Init(NULL, RUN_SYNC);
		cmd1.SetIgnoreActiveItem(TRUE);
		cmd1.Run( P4CHANGE_DEL, txt );
		cmd1.CloseConn(&e);
	}
}

BOOL CCmd_Integrate::Run(BOOL reverse, LPCTSTR branchName)
{
	m_Reverse= reverse;

	ClearArgs();
	AddArg(_T("integ"));
	if (GET_SERVERLEVEL() >= 18)	// 2004.1 server or later?
		AddArg(_T("-o"));
	
	if(m_Reverse)
		AddArg(_T("-r"));
	
	if(branchName == NULL)
	{
		ASSERT(0);
		return FALSE;
	}
	
	AddArg(_T("-b"));
	AddArg(branchName);

	m_MultipleItems=FALSE;
	m_IsBranch= TRUE;
	m_IsPreview= FALSE;
	
	return CP4Command::Run();
}

BOOL CCmd_Integrate::Run( CStringList *source, CStringList *target,
				LPCTSTR reference, 
				LPCTSTR revRange,
				LPCTSTR commonPath,
				BOOL isBranch, BOOL isReverse, BOOL isNoCopy,
				BOOL isForced, BOOL isForcedDirect, BOOL isRename, BOOL isPreview,
				int changeNum, BOOL isPermitDelReadd, int delReaddType, 
				BOOL isBaselessMerge,  BOOL isIndirectMerge, 
				BOOL isPropagateTypes, BOOL isBaseViaDelReadd, 
				int branchFlag, BOOL bDontSync, BOOL bBiDir )
{
	ASSERT( source != NULL && source->IsKindOf( RUNTIME_CLASS(CStringList) ) );
	ASSERT( target != NULL && target->IsKindOf( RUNTIME_CLASS(CStringList) ) );
	if (!isBranch)
	{
		ASSERT( source->GetCount() == 1 || target->GetCount() == 1 );
		ASSERT( source->GetCount() > 0 );
		ASSERT( target->GetCount() > 0 );
	}
	else
		ASSERT( source->GetCount() <= 1 || target->GetCount() <= 1 );

	// With pre-99.1 server, can only spec source if not a branch
	if( GET_SERVERLEVEL() < LEVEL_NEWINTEG )
		ASSERT( !isBranch || ( source->GetCount() == 1 && source->GetHead() == _T("//...") ) );

	if( GET_SERVERLEVEL() >= LEVEL_NEWINTEG 
		&& isBranch && (branchFlag == 1) 
		&& source->GetCount() > 0 )
		m_NewBranchSyntax= TRUE;
	else
		m_NewBranchSyntax= FALSE;
		
	ClearArgs();
	m_BaseArgs= AddArg(_T("integ"));
	if (GET_SERVERLEVEL() >= 18)	// 2004.1 server or later?
		AddArg(_T("-o"));
	if(changeNum > 0)
	{
		AddArg(_T("-c"));
		m_BaseArgs= AddArg(m_ChangeNbr = changeNum);
	}
	if(isReverse)
	{
		ASSERT(!m_NewBranchSyntax);
		m_BaseArgs= AddArg(_T("-r"));
	}
	if(isNoCopy)
		m_BaseArgs= AddArg(_T("-v"));
	if(isPermitDelReadd && ( GET_SERVERLEVEL() > 7 ))
	{
		if (delReaddType != 2 && GET_SERVERLEVEL() > 16)
			m_BaseArgs= AddArg(delReaddType == 1 ? _T("-Dt") : _T("-Ds"));
		else
			m_BaseArgs= AddArg(_T("-d"));
	}
	if(isIndirectMerge && ( GET_SERVERLEVEL() > 14 ))
		m_BaseArgs= AddArg(_T("-I"));
	else if(isBaselessMerge && ( GET_SERVERLEVEL() > 7 ))
		m_BaseArgs= AddArg(_T("-i"));
	if(isPropagateTypes && ( GET_SERVERLEVEL() > 9 ))
		m_BaseArgs= AddArg(_T("-t"));
	if(isBaseViaDelReadd && ( GET_SERVERLEVEL() > 17 ))
		m_BaseArgs= AddArg(_T("-Di"));
	if(isForced)
		m_BaseArgs= AddArg(_T("-f"));
	if(isForcedDirect&& ( GET_SERVERLEVEL() > 17 ))
		m_BaseArgs= AddArg(_T("-1"));
	if(isPreview)
		m_BaseArgs= AddArg(_T("-n"));
	if(bDontSync && ( GET_SERVERLEVEL() >= 13 ))
		m_BaseArgs= AddArg(_T("-h"));

	m_IsBranch= isBranch;
	m_Reference= reference;
	m_IsPreview= isPreview;
	m_IsRename = isRename;
	m_RevRange= revRange;
	m_CommonPath = commonPath;
	m_BiDir = bBiDir;

	if(isBranch)
	{
		AddArg(_T("-b"));
		m_BaseArgs= AddArg(reference);
	}
	else
	{
		m_Reference= reference;
	}

	if( m_NewBranchSyntax || m_BiDir )
		m_BaseArgs= AddArg(_T("-s"));

	// Initialize last message time
	m_LastMessage= GetTickCount();
	m_pStatusArray= new CStringArray();
	
	if( !source->GetCount() && !m_NewBranchSyntax)
	{
		// We are integrating from a branch
		if ( target->GetCount() == 1 )
		{
			CString tgt = target->GetHead();
			tgt += m_RevRange;
			AddArg( tgt );
		}
		else if ( target->GetCount() > 1 )
		{
			// I can't figure out any way to make this code execute
			// But if it does, it'll work fine - unless a rev range is given
			// Unfortunately, the rev range will be ignored
			ASSERT(m_RevRange.IsEmpty());
			m_SourceProvided= FALSE;
			m_posStrListIn= target->GetHeadPosition();
			m_pStrListIn= target;  
			m_MultipleItems= (m_pStrListIn->GetCount() > 1);
		
			// Put the first few files into the arg list
			NextListArgs();
		}
		else if (!m_RevRange.IsEmpty())
			AddArg( m_RevRange );
		return CP4Command::Run();
	}
	else if( ( source->GetCount() == 1 && source->GetHead() == _T("//...") ) &&
			 ( target->GetCount() == 1 && target->GetHead() == _T("//...") ) )
	{
		// If we are integrating an entire branch, there are no list args
		CString src= source->GetHead();
		src+= m_RevRange;
		AddArg( src );
		AddArg( target->GetHead() );
		return CP4Command::Run();
	}
	else if ( source->GetCount() == 1 && source->GetHead() == _T("//...") 
		   && target->GetCount() == 0 && !m_RevRange.IsEmpty() )
	{
		// If we are integrating an entire branch, there are no list args
		CString src= m_RevRange;
		AddArg( src );
		return CP4Command::Run();
	}
	else
	{
		// Set the arg list tracking info
		if( source->GetHead() != _T("//...") )
		{
			m_SourceProvided= TRUE;
			m_posStrListIn= source->GetHeadPosition();
			m_pStrListIn= source;  
		}
		else
		{
			m_SourceProvided= FALSE;
			m_posStrListIn= target->GetHeadPosition();
			m_pStrListIn= target;  
		}
		m_MultipleItems= (m_pStrListIn->GetCount() > 1);
	
		// Put the first few files into the arg list
		NextListArgs();
	
		return CP4Command::Run();
	}
}

BOOL CCmd_Integrate::NextListArgs()
{
    ASSERT(m_BaseArgs <= GetArgc());

	ClearArgs(m_BaseArgs);  // Clear all but the base args
	if(m_NewBranchSyntax && m_SourceProvided)
	{
		// Add the source side, with revision range
		CString src=m_pStrListIn->GetNext(m_posStrListIn);
		src+= m_RevRange;
		AddArg(src);

		// Add the target side
		AddArg(_T("//..."));
	}
	else if(m_NewBranchSyntax)
	{
		// Add the source side, with revision range
		CString src= _T("//...");
		src+= m_RevRange;
		AddArg( src );

		// Add the target side
		if (m_pStrListIn->GetCount() > 0)
		{
			CString targ=m_pStrListIn->GetNext(m_posStrListIn);
			AddArg( targ );
		}
	}
	else if(m_IsBranch)
	{
		// Add the target, with revision range
		if (m_pStrListIn->GetCount() > 0)
		{
			CString targ=m_pStrListIn->GetNext(m_posStrListIn);
			targ+= m_RevRange;
			AddArg(targ);
		}
	}
	else
	{
		// Pull a single arg off the list
		if( m_posStrListIn != NULL )	
		{
			// Add the source side
			CString src=m_pStrListIn->GetNext(m_posStrListIn);
			AddArg(src + m_RevRange);

			// Make a dest side
			CString dest=m_Reference;

			// figure out the slash char (src) and the wildcard string (dest)
			TCHAR   slashChr = GET_P4REGPTR( )->ShowEntireDepot( ) > SDF_DEPOT ? _T('\\') : _T('/');
			TCHAR   wildChr  = slashChr;
			if (src.Find(slashChr) == -1)
			{
				if (src.Find(_T('/')) != -1)
					slashChr = _T('/');
				else if (src.Find(_T('\\')) != -1)
					slashChr = _T('\\');
			}
			if (dest.Find(wildChr) == -1)
			{
				if (dest.Find(_T('/')) != -1)
					wildChr = _T('/');
				else if (dest.Find(_T('\\')) != -1)
					wildChr = _T('\\');
			}
			ASSERT(slashChr == wildChr);
			CString wildcard = CString(wildChr) + _T("...");

			int lastpart;
			int wildDest= dest.Find(wildcard);
			int wildSrc= src.Find(wildcard);

			if(wildSrc != -1 && wildDest != -1 && m_MultipleItems)
			{
				/////////////////////
				// the source directory will be created as a subdir under dest

				// 1) cut off wildcards
				src= src.Left(wildSrc);
				dest= dest.Left(wildDest);
				// 2) find what comes after the common path in src
				if ((FindNoCase(src, m_CommonPath) == 0)
				 && (src.GetLength() == m_CommonPath.GetLength() 
				  || src.GetAt(m_CommonPath.GetLength()) == slashChr))
					 lastpart= m_CommonPath.GetLength();
				else lastpart= src.ReverseFind(slashChr);
				// 3) add subdirname to dest
				dest+= src.Mid(lastpart);
				// 4) put wildcard back on
				dest+= wildcard;
			}
			else if(wildSrc != -1 && wildDest != -1)
			{
				/////////////////////
				// the source directory is the only selected item, so its files
				// are integed directly to dest dir, without creating a subdir
			
				// Nothing to do, since wildcards already match
				
			}
			else if(wildSrc == -1 && wildDest != -1)
			{
				/////////////////////
				// the source item is a file (no wildcard) but the dest
				// item has a wildcard
				
				// 1) cut off the dest wildcard
				dest= dest.Left(wildDest);
				// 2) find what comes after the common path in src
				if ((FindNoCase(src, m_CommonPath) == 0)
				 && (src.GetLength() == m_CommonPath.GetLength() 
				  || src.GetAt(m_CommonPath.GetLength()) == slashChr))
					 lastpart= m_CommonPath.GetLength();
				else lastpart= src.ReverseFind(slashChr);
				// 3) add filename to dest
				dest+= src.Mid(lastpart);
			}
			else if(wildSrc == -1 && wildDest == -1)
			{
				/////////////////////
				// both src and dest are discreet files

				// Nothing to do, since wildcards already match
			}
			else
			{
				/////////////////////
				// We should have already addressed all legal possibilities for
				// wildcard levels.  We were given bad args!
				ASSERT(0);
			}
			
			// Make the target arg
			AddArg(dest);
		}
	}

	// Caller knows not to call again when the list is empty
	if(m_posStrListIn == NULL)
		m_pStrListIn->RemoveAll();
	return FALSE;
}


void CCmd_Integrate::OnOutputInfo(char level, LPCTSTR data, LPCTSTR msg)
{
	BOOL	bDoStatusAdd = TRUE;

    CString msgD = data;
	CString msgM = msg;

	if (StrStr(data, _T(" - can't ")))
	{
		if ( StrStr(data, _T("can't integrate")) 
          || StrStr(data, _T("can't branch"))
          || StrStr(data, _T("can't delete")) )
		{
			LPCTSTR i;

			if ((i = StrStr(data, _T(" without -i flag"))) != 0)
			{
				msgD = CString(data, i - data) + LoadStringResource(IDS_ENABLE_BASELESS_MERGES);
				if (msg != data)
					msgM += LoadStringResource(IDS_ENABLE_BASELESS_MERGES);
			}
			else if ((i = StrStr(data, _T(" without -d flag"))) != 0)
			{
				msgD = CString(data, i - data) + LoadStringResource(IDS_ENABLE_DELETE_READD);
				if (msg != data)
					msgM += LoadStringResource(IDS_ENABLE_DELETE_READD);
			}
		}
		bDoStatusAdd = FALSE;
	}
	if (msg == data)
		msgM = msgD;
	
	if (bDoStatusAdd)
	{
		CString txt;
		if(m_IsPreview)
			txt = LoadStringResource(IDS_INTEGRATE_PREVIEW);

		txt += msgM;

		// Dont go compute bound by passing one message per row
		if (m_ClobberFailed)
			txt= LoadStringResource(IDS_FAILED) + txt;
		m_pStatusArray->Add(txt);
		if( GetTickCount() - m_LastMessage > 250)
		{
			TheApp()->StatusAdd( m_pStatusArray);
			m_pStatusArray= new CStringArray;
			m_LastMessage= GetTickCount();
		}
	}
	if (m_ClobberFailed)
		m_ClobberFailed = FALSE;
	else
		m_List.AddHead(msgD);
}

void CCmd_Integrate::PostProcess()
{
	if( m_pStatusArray->GetSize() )
		TheApp()->StatusAdd( m_pStatusArray);
	else
		delete m_pStatusArray;
}

BOOL CCmd_Integrate::HandledCmdSpecificError(LPCTSTR errBuf, LPCTSTR errMsg)
{
	BOOL handledError=FALSE;
	if (StrStr(errBuf, _T("Can't clobber writable file")) == errBuf)
	{
		if (m_pStatusArray->GetSize() > 0)
		{
			TheApp()->StatusAdd( m_pStatusArray);
			m_pStatusArray= new CStringArray;
			m_LastMessage= GetTickCount();
		}
		m_ClobberFailed = TRUE;
	}
	else if (StrStr(errBuf, 
		_T("The filename, directory name, or volume label syntax is incorrect")) != NULL)
	{
		if (m_pStatusArray->GetSize() > 0)
		{
			TheApp()->StatusAdd( m_pStatusArray);
			m_pStatusArray= new CStringArray;
			m_LastMessage= GetTickCount();
		}
		m_ClobberFailed = TRUE;
	}

	if (m_IsRename && m_ChangeNbr)
		m_DeleteChg = TRUE;
	
	return handledError;
}

void CCmd_Integrate::OnOutputError(char level, LPCTSTR errBuf, LPCTSTR errMsg)
{
	CP4Command::OnOutputError(level, errBuf, errMsg);
	if (m_FatalError)
	{
		if (level < 0x50)
		{
			m_FatalError = FALSE;
			m_FatalErrorCleared = TRUE;
		}
		else
			delete m_pStatusArray;
	}
}