//
// Copyright 1998 Perforce Software.  All rights reserved.
//
//

// Cmd_DirStat.cpp

#include "stdafx.h"
#include "p4win.h"
#include "MainFrm.h"
#include "Cmd_Dirstat.h"
#include "Cmd_Fstat.h"
#include "Cmd_Dirs.h"
#include "Cmd_Where.h"
#include "strops.h"

#pragma warning (disable:4786)
#include <list>
#include <string>

using namespace std;

#ifdef UNICODE
typedef list<wstring> LISTSTR;
#else
typedef list<string> LISTSTR;
#endif

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


IMPLEMENT_DYNCREATE(CCmd_DirStat, CP4Command)


CCmd_DirStat::CCmd_DirStat(CGuiClient *client) : CP4Command(client)
{
	m_ReplyMsg= WM_P4DIRSTAT;
	m_TaskName= _T("DirStat");
	m_ShowOnlyNotInDepot = MainFrame()->m_ShowOnlyNotInDepot;
}

BOOL CCmd_DirStat::Run( CStringList *specs, BOOL entireDepot )
{
	m_EntireDepot= entireDepot;
	m_pSpecList=specs;
	return CP4Command::Run();
}

void CCmd_DirStat::PreProcess(BOOL& done)
{
	BOOL bSpecListHasWild = FALSE;
	BOOL bFileListHasWild = FALSE;
	CStringList files;
    POSITION pos;
	Error e;
	
	if (GET_P4REGPTR( )->ShowEntireDepot( ) <= SDF_DEPOT)
	{
		// Make a copy of the specList, since Cmd_Dirs will 
		// delete the list as it is processed
		CStringList specListCopy;
		for( pos= m_pSpecList->GetHeadPosition(); pos!= NULL; )
			specListCopy.AddHead( m_pSpecList->GetNext(pos) );

		// Set up and run Dirs synchronously
		CCmd_Dirs cmd1(m_pClient);
		cmd1.Init(NULL, RUN_SYNC);
		if(cmd1.Run( &specListCopy, m_EntireDepot ))
		{
			m_FatalError= cmd1.GetError();
			done=TRUE;
		}
		else
		{
			m_ErrorTxt= "Unable to Run Dirs";
			done= m_FatalError=TRUE;
		}
		cmd1.CloseConn(&e);

		CStringList *list= cmd1.GetList();
    
		// Copy the results from dirs
		if(!m_FatalError && list->GetCount() > 0)
		{
			for( pos= list->GetHeadPosition(); pos!= NULL; )
				m_Dirs.AddHead( list->GetNext(pos) );
		}
	}
	else
	{
		CString theroot = TheApp()->m_ClientRoot;
		if (GetFileAttributes(theroot) == -1)
		{
			done = TRUE;
			return;
		}
		TCHAR curdir[MAX_PATH+1];
		if ((theroot.GetAt(0) == _T('\\')) && GetCurrentDirectory(sizeof(curdir)/sizeof(TCHAR), curdir))
		{
			curdir[2] = _T('\0');
			theroot = curdir + theroot;
			TheApp()->m_ClientRoot = theroot;
		}
		if (theroot.GetLength() > 3)
			theroot += _T('\\');
		int therootlgth = theroot.GetLength();

		CFileFind finder;

		// find the directories
		for( pos= m_pSpecList->GetHeadPosition(); pos!= NULL; )
		{
			CString dirname = m_pSpecList->GetNext(pos);
			if (dirname.FindOneOf(_T("@#%")) != -1)
				bSpecListHasWild = TRUE;
			if (dirname.GetAt(0) == _T('/'))
				continue;	// we don't handle depot syntax here
			else
				dirname.Replace(_T('/'), _T('\\'));

			// check to make sure the desired directory
			// is actually under the client's root dir
			if (_tcsnicmp(dirname, theroot, therootlgth))
				continue;

			// now find all the dirs and files in the directory
			BOOL bWorking = finder.FindFile(dirname);

			while (bWorking)
			{
				bWorking = finder.FindNextFile();

				// skip . and .. files
				if (finder.IsDots())
					continue;

				CString str = finder.GetFilePath();

				// we can't deal with files or directories 
				// with * in their name, so skip 'em
				if (str.Find(_T('*')) != -1)
				{
					CString txt;
					txt.FormatMessage(IDS_CANTDEALWITH_ASTERISK, str);
					TheApp()->StatusAdd(txt, SV_WARNING);
					continue;
				}

				// if it's a directory, add it to the command's dirs list
				if (finder.IsDirectory())
				{
					m_Dirs.AddHead( str );
				}
				// if it's a regular file, save it for later processing
				// in the fstat part of this routine below
				else if ((GET_P4REGPTR( )->ShowEntireDepot( ) == SDF_LOCALTREE)
					&& (!finder.IsHidden() || GET_P4REGPTR( )->ShowHiddenFilesNotInDepot())
					&& !finder.IsSystem())
				{
					if (str.FindOneOf(_T("@#%")) != -1)
						bFileListHasWild = TRUE;
					files.AddTail( str );
				}
			}
		}

		finder.Close();

		// if we only want to see Perforce files we now need to eliminate
		// all the directories that don't have Perforce files - so run p4 dirs
		if (m_Dirs.GetCount())
		{
			// Save a copy of the m_Dirs, since Cmd_Dirs will 
			// delete the list as it is processed
			BOOL bWild = FALSE;
			BOOL b = GET_P4REGPTR( )->ShowEntireDepot( ) > SDF_DEPOT;	// Local View?
			CStringList saveDirs;
			for( pos= m_Dirs.GetHeadPosition(); pos!= NULL; )
			{
				CString str = m_Dirs.GetNext(pos);
				if (b && str.FindOneOf(_T("@#%")) != -1)
					bWild = TRUE;
				saveDirs.AddHead( str );
			}
			if (bWild)
			{
				m_Dirs.RemoveAll();
				for( pos= saveDirs.GetHeadPosition(); pos!= NULL; )
				{
					CString str =  saveDirs.GetNext(pos);
					if (b && str.FindOneOf(_T("@#%")) != -1)
					{
						int star = str.Find(_T('*'));
						StrBuf b;
						StrBuf f;
						f << CharFromCString(str);
						StrPtr *p = &f;
						StrOps::WildToStr(*p, b);
						str = CharToCString(b.Value());
						if (star != -1)
						{
							while ((star = str.Find(_T("%2A"))) != -1)
							{
								CString s1 = str.Left(star);
								CString s2 = str.Mid(star + sizeof(_T("%2A")));
								str = s1 + _T('*') + s2;
							}
						}
					}
					m_Dirs.AddHead( str );
				}
			}

			// Set up and run Dirs synchronously
			CCmd_Dirs cmd1(m_pClient);
			cmd1.Init(NULL, RUN_SYNC);
			if(cmd1.Run( &m_Dirs, m_EntireDepot ))
			{
				m_FatalError= cmd1.GetError();
				done=TRUE;
			}
			else
			{
				m_ErrorTxt= "Unable to Run Dirs";
				done= m_FatalError=TRUE;
			}
			cmd1.CloseConn(&e);

			// all we care about is the error listing
			CStringList *list= cmd1.GetErrors();
    
			// Make sure m_Dirs was emptied
			m_Dirs.RemoveAll();

			// Look thru the directories and remove any
			// mentioned in an error message
			if(!m_FatalError && list->GetCount() > 0)
			{
				int lgth = TheApp()->m_ClientRoot.GetLength();
				CStringList fstatlist;
				CStringList fdirslist;
				POSITION pos2;
				for( pos = saveDirs.GetHeadPosition(); pos!= NULL; )
				{
					CString dir = saveDirs.GetNext(pos);
					BOOL bAdd = TRUE;
					for( pos2 = list->GetHeadPosition(); pos2 != NULL; )
					{
						CString errmsg = list->GetNext(pos2);
						if ((tolower(dir.GetAt(lgth)) == tolower(errmsg.GetAt(lgth)))
						  && errmsg.Find(dir) == 0)
						{
							if (errmsg.Find(dir + _T(" - file(s) not in client view")) == 0)
							{
								if (GET_SERVERLEVEL() >= 25)	// 2008.1 or later?
								{
									CString dirdotdotdot = dir + _T("\\...");
									fstatlist.AddTail(dirdotdotdot);
									fdirslist.AddTail(dir);
								}
								bAdd = FALSE;
							}
							else if (errmsg.Find(dir + _T(" - no mappings in client view")) == 0)
								m_NotMapped.AddHead(dir);
							break;
						}
					}
					if (bAdd)
						m_Dirs.AddHead(dir);
				}

				if (fstatlist.GetCount())
				{
					CCmd_Fstat cmd2(m_pClient);
					cmd2.Init(NULL, RUN_SYNC);
					if(cmd2.Run( FALSE, &fstatlist, m_EntireDepot, 0, FALSE, -1, 1 ))
					{
						if (cmd2.GetFileList()->GetCount())
						{
							CObList *fstatList= cmd2.GetFileList();
							for( pos= fstatList->GetHeadPosition(); pos!= NULL; )
							{
								CP4FileStats *stats = ( CP4FileStats * )fstatList->GetNext( pos );
								ASSERT_KINDOF( CP4FileStats, stats );
								CString clientPath = stats->GetFullClientPath( );
								for( pos2= fdirslist.GetHeadPosition(); pos2!= NULL; )
								{
									CString dir = fdirslist.GetNext(pos2);
									CString dirslash = dir + _T("\\");
									CString path = clientPath.Left(dirslash.GetLength());
									if (path == dirslash)
									{
										m_Dirs.AddHead(dir);
										break;
									}
								}
								delete stats;
							}
						}
					}
					else
					{
						m_ErrorTxt= _T("Unable to Run Fstat");
						m_FatalError=TRUE;
					}
				}
			}
			else	// we have to put the saved dirs back in m_Dirs
			{
				for( pos = saveDirs.GetHeadPosition(); pos!= NULL; )
					m_Dirs.AddHead(saveDirs.GetNext(pos));
			}
		}
	}

	if(!m_FatalError)
	{
     	// Set up and run fstat
		// Fisrt convert any wild syntax if there is any
		CStringList *pstrlist = m_pSpecList;
		CStringList strlist;
		if (bSpecListHasWild)
		{
			for( pos= m_pSpecList->GetHeadPosition(); pos!= NULL; )
			{
				CString str = m_pSpecList->GetNext(pos);
				if (str.FindOneOf(_T("@#%")) != -1)
				{
					int star = str.Find(_T('*'));
					StrBuf b;
					StrBuf f;
					f << CharFromCString(str);
					StrPtr *p = &f;
					StrOps::WildToStr(*p, b);
					str = CharToCString(b.Value());
					if (star != -1)
					{
						while ((star = str.Find(_T("%2A"))) != -1)
						{
							CString s1 = str.Left(star);
							CString s2 = str.Mid(star + sizeof(_T("%2A")));
							str = s1 + _T('*') + s2;
						}
					}
				}
				strlist.AddTail( str );
			}
			pstrlist = &strlist;
		}
		// Now run the Fstat
	    CCmd_Fstat cmd2(m_pClient);
		cmd2.Init(NULL, RUN_SYNC);
		if(cmd2.Run( FALSE, pstrlist, m_EntireDepot, 0, FALSE, -1, -1 ))
            m_FatalError= cmd2.GetError();
        else
		{
			m_ErrorTxt= _T("Unable to Run Fstat");
			m_FatalError=TRUE;
		}

        CObList *fstatList= cmd2.GetFileList();
		    
	    // Copy the results from fstat
	    if(!m_FatalError && fstatList->GetCount() > 0)
	    {
			if (GET_P4REGPTR( )->ShowEntireDepot( ) == SDF_LOCALTREE)
			{
				// We need to generate 2 sorted lists:
				LISTSTR filesarray;		// 1) the 'files' names
				LISTSTR fstatarray;		// 2) the pFileStats->GetFullClientPath() names
				LISTSTR::iterator ifi;	// iterator for filesarray
				LISTSTR::iterator ifs;	// iterator for fstatarray

				for (pos = files.GetHeadPosition(); pos != NULL; )
				{
					filesarray.insert(filesarray.end(), 
						(TCHAR *)(files.GetNext( pos ).GetBuffer()));
				}
				filesarray.sort();

			    for( pos= fstatList->GetHeadPosition(); pos!= NULL; )
				{
				    CObject *cobject = fstatList->GetNext(pos);
				    m_Files.AddHead( cobject );
					if (((CP4FileStats *)cobject)->GetHeadAction() != F_DELETE)
						fstatarray.insert(fstatarray.end(), 
							(TCHAR *)(((CP4FileStats *)cobject)->GetFullClientPath()));
				}
				fstatarray.sort();

				// now walk both arrays at the same time
				// add any file not in the fstatarray to the 'files' list
				files.RemoveAll();
				ifi = filesarray.begin();
				ifs = fstatarray.begin();
				while (ifi != filesarray.end())
				{
#ifdef _DEBUG
					if (ifs != fstatarray.end())
					{
						CString ifistr = (*ifi).c_str();
						CString ifsstr = (*ifs).c_str();
						int i = fstatarray.size();
						i--;	// to shutup the compiler
					}
#endif
					if (ifs != fstatarray.end() 
					 && Compare((*ifi).c_str(), (*ifs).c_str())==0)
					{
						ifi++;						// found it in the fstats
						fstatarray.remove((*ifs).c_str());// remove it from the fstat list
						ifs = fstatarray.begin();	// setup for next item
						continue;
					}
					if (ifs != fstatarray.end())	// keep checking if not at end of fstat list
					{
						ifs++;						// next fstat item
						continue;
					}
					files.AddHead( (*ifi++).c_str() );	// not in fstat list; add to not in depot list
					ifs = fstatarray.begin();			// setup for next item
				}
			}
			else
			{
				for( pos= fstatList->GetHeadPosition(); pos!= NULL; )
				{
					CObject *cobject = fstatList->GetNext(pos);
					m_Files.AddHead( cobject );
				}
			}
        }

		if (GET_P4REGPTR( )->ShowEntireDepot( ) > SDF_DEPOT)
		{
			// Now see if there were any " - no mappings in client view" errors
			// if so, add those directories to the 'm_NotMapped' list
			CStringList *list= cmd2.GetErrors();
			if (list->GetCount())
			{
				for(POSITION pos = list->GetHeadPosition(); pos != NULL; )
				{
					int i;
					CString errmsg = list->GetNext(pos);
					if ((i = errmsg.Find(_T('*'))) > 0)
					{
						errmsg = errmsg.Left(i);
						if (errmsg.GetLength() != 3)	// handle C:\ == root
							TrimRightMBCS(errmsg, _T("\\"));
						m_NotMapped.AddHead(errmsg);
					}
				}
			}
		}

		if (GET_P4REGPTR( )->ShowEntireDepot( ) == SDF_LOCALTREE)
		{
			// 'files' contains a list of files that are 
			// under the root but not under Perforce control.
			// 'm_NotMapped' contains a list of directories
			// that are not in the client
			// Now remove any files that cannot be added to the client
			POSITION pos;
			POSITION pos1, pos2;
			if (files.GetCount() && m_NotMapped.GetCount())
			{
				int i;
				for (pos = m_NotMapped.GetHeadPosition(); pos != NULL; )
				{
					CString dir = m_NotMapped.GetNext( pos );
					for (pos1 = files.GetHeadPosition(); ( pos2 = pos1 ) != NULL; )
					{
						CString file = files.GetNext( pos1 );
						if ((i = ReverseFindMBCS(file, _T('\\'))) == -1)
							continue;
						file = file.Left(i);
						if (file == dir)
						{
							// Don't mess with pos1!
							files.RemoveAt( pos2 );
						}
					}
				}
			}
			if (files.GetCount())
			{
				cmd2.CloseConn(&e);

				// if any addable files contain @ # or %, we have to convert them
				if (bFileListHasWild)
				{
					BOOL bWild = FALSE;
					CStringList strlist;
					for (POSITION pos = files.GetHeadPosition(); pos != NULL; )
					{
						CString file = files.GetNext( pos );
						if (file.FindOneOf(_T("@#%")) != -1)
							bWild = TRUE;
						strlist.AddHead( file );
					}
					if (bWild)
					{
						files.RemoveAll();
						for (POSITION pos = strlist.GetHeadPosition(); pos != NULL; )
						{
							CString file = strlist.GetNext( pos );
							if (file.FindOneOf(_T("@#%")) != -1)
							{
								int star = file.Find(_T('*'));
								StrBuf b;
								StrBuf f;
								f << CharFromCString(file);
								StrPtr *p = &f;
								StrOps::WildToStr(*p, b);
								file = CharToCString(b.Value());
								if (star != -1)
								{
									while ((star = file.Find(_T("%2A"))) != -1)
									{
										CString s1 = file.Left(star);
										CString s2 = file.Mid(star + sizeof(_T("%2A")));
										file = s1 + _T('*') + s2;
									}
								}
							}
							files.AddHead( file );
						}
					}
				}

				// Now create FileStats for all the addable files
				// First run 'p4 where' on all the files to
				// get both the local and the depot syntax
				CCmd_Where cmd3(m_pClient);
				cmd3.Init(NULL, RUN_SYNC);
				if ( cmd3.Run(&files) && !cmd3.GetError() )
				{
					CStringList *locals = cmd3.GetLocalFiles();
					CStringList *depots = cmd3.GetDepotFiles();
					ASSERT(locals->GetCount() == depots->GetCount());

					// get the fstat of files opened for add
					CObList *list = (CObList *)::SendMessage(m_ReplyWnd, WM_GETADDFSTATS, 0, 0);
					ASSERT_KINDOF(CObList, list);
					int listcount = list->GetCount();

					// now walk the 'p4 where' output
					CString localprev = _T("");
					for (pos1 = locals->GetHeadPosition(), 
						 pos2 = depots->GetHeadPosition(); pos1 != NULL; )
					{
						CString localsyntax = locals->GetNext( pos1 );
						CString depotsyntax = depots->GetNext( pos2 );

						// check for duplicates (due to a + mapping in clinet's view)
						if (localprev == localsyntax)
							continue;
						localprev = localsyntax;

						// do we have an unmap record?
						if (depotsyntax.GetAt(0) == _T('-'))
						{
							depotsyntax = depotsyntax.Mid(1);
							POSITION pos= m_Files.GetHeadPosition();
							while(pos != NULL)
							{
								// Get the filestats info for files opened for add
								CP4FileStats *stats = (CP4FileStats *) m_Files.GetNext(pos);
								if ( depotsyntax == stats->GetFullDepotPath() )
								{
									// since the obvious m_Files.RemoveAt(pos) doesn't work
									// we just mark the record as deleted 
									// by setting the depotpat to the empty string
									stats->SetDepotPath(_T(""));
									break;
								}
							}
							// clear the 'localprev' since we might
							// subsequently map this same file back in
							localprev = _T("-");
							// we certainly don't want to add
							// a new fstat record for an unmap
							continue;
						}

						CP4FileStats *newStats= new CP4FileStats;
						BOOL b = FALSE;
						// if there are files opened for add, 
						// we need to check that list before creating a
						// whole new fstat record from the bare 'p4 where' data
						if (listcount)
						{
							POSITION pos= list->GetHeadPosition();
							while(pos != NULL)
							{
								// Get the filestats info for files opened for add
								CP4FileStats *stats = (CP4FileStats *) list->GetNext(pos);
								ASSERT_KINDOF(CP4FileStats, stats);

								// is this file which has been added the same as 
								// the file we are examining from the 'p4 where' list?
								if ( depotsyntax == stats->GetFullDepotPath() )
								{
									// Create a copy of the fstat info
									newStats->Create(stats);
									newStats->SetClientPath(localsyntax);
									newStats->SetNotInDepot(TRUE);
									newStats->SetHaveRev(0);
									listcount--;
									b = TRUE;
									break;
								}
							}
						}
						// If we didn't create a new fstat because it wasn't open for add
						// we have to create a sparse fstat record from the 'p4 where' data
						if(!b && !newStats->Create(localsyntax, depotsyntax))
							delete newStats;
						else
						{
							// now add out newly created fstat record to the list
							CObject *cobject = (CP4FileStats *)newStats;
							m_Files.AddHead( cobject );
						}
					}
				}
				else
					TheApp()->StatusAdd( LoadStringResource(IDS_UNABLE_TO_SHOW_LOCAL_FILES), SV_WARNING );  
			}
		}
	}
	
    done=TRUE;
}