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

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

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

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- March 1998
 */

/*
 * LogParse.cpp : parse the cvs log output
 */

#include "stdafx.h"

#include <errno.h>
#include <stdio.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef WIN32
#	include "wincvs.h"
#	include "GraphDoc.h"
#endif /* WIN32 */

#ifdef TARGET_OS_MAC
#	include <unistd.h>
#	include "MacCvsApp.h"
#	include "CTreeView.h"
# include "MacMisc.h"
#endif /* TARGET_OS_MAC */

#if qUnix
#	include "UCvsDialogs.h"
#	include "UCvsGraph.h"
#endif

#include "LogParse.h"
#include "CvsArgs.h"
#include "CvsLog.h"
#include "AppGlue.h"
#include "AppConsole.h"
#include "CPStr.h"
#include "getline.h"

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

#ifdef qMacCvsPP
  CPersistentInt    sCommitMsgEncoding("P_MessageEncoding", kMsgEncodingMacOSRoman, kAddSettings);
#endif


/*!
	Print the file revision information
	\param out Console
	\param rev File revision
*/
static void print(CColorConsole& out, const CRevFile& rev)
{
	out << kBlue << "----------------------------" << kNormal << kNL;
	out << "Revision : " << kBold << kRed << rev.RevNum().c_str() << kNormal << kNL;
	
	if( !rev.Locker().empty() )
		out << "Locked by : '" << kBold << rev.Locker() << kNormal << '\'' << kNL;

	out << "Date : " << kBold <<
		(rev.RevTime().tm_year + 1900) << '/' <<
		rev.RevTime().tm_mon << '/' <<
		rev.RevTime().tm_mday << ' ' <<
		rev.RevTime().tm_hour << ':' <<
		rev.RevTime().tm_min << ':' <<
		rev.RevTime().tm_sec << kNormal << kNL;

	out << "Author : '" << kBold << rev.Author() << kNormal << '\'' << kNL;
	out << "State : '" << kBold << rev.State() << kNormal << '\'' << kNL;
	out << "Lines : +" << kBold << rev.ChgPos() << ' ' << rev.ChgNeg() << kNormal << kNL;

	if( !rev.BranchesList().empty() )
	{
		out << "Branches :" << kNL;

		std::vector<CRevNumber>::const_iterator s;
		for(s = rev.BranchesList().begin(); s != rev.BranchesList().end(); ++s)
		{
			const CRevNumber& branch = *s;
			out << '\t' << kBold << branch.c_str() << kNormal << kNL;
		}
	}
	
	if( !rev.KeywordSubst().empty() )
		out << "Keyword : '" << kBold << rev.KeywordSubst() << kNormal << '\'' << kNL;
	
	if( !rev.CommitID().empty() )
		out << "CommitID : '" << kBold << rev.CommitID() << kNormal << '\'' << kNL;

	if( !rev.MergePoint().empty() )
		out << "MergePoint : '" << kBold << rev.MergePoint().c_str() << kNormal << '\'' << kNL;

	out << "Description :" << kNL << kBrown;
#ifdef qMacCvsPP
  if ( sCommitMsgEncoding == kMsgEncodingMacOSRoman ) out << rev.DescLog();
  else out << ConvertString(rev.DescLog(), sCommitMsgEncoding == kMsgEncodingUTF8 ? kCFStringEncodingUTF8 : kCFStringEncodingISOLatin1, kCFStringEncodingMacRoman, true);
#else
	out << rev.DescLog();
#endif
	out << kNormal << kNL;
}

/*!
	Print the file RCS information
	\param out Console
	\param rcs RCS file
	\param recurse true for recursive, false otherwise
*/
static void print(CColorConsole& out, const CRcsFile& rcs, bool recurse = true)
{
	out << "Rcs file : '" << kRed << rcs.RcsFile() << kNormal << '\'' << kNL;
	out << "Working file : '" << kBold << rcs.WorkingFile() << kNormal << '\'' << kNL;
	out << "Head revision : " << kBold << kRed << rcs.HeadRev().c_str() << kNormal << kNL;
	out << "Branch revision : " << kBold << rcs.BranchRev().c_str() << kNormal << kNL;

	out << "Locks :" << kBold << (rcs.LockStrict() ? " strict" : "") << kNormal << kNL;

	std::vector<CRevNumber>::const_iterator s;
	for(s = rcs.LocksList().begin(); s != rcs.LocksList().end(); ++s)
	{
		const CRevNumber & lock = *s;
		out << '\t' << kBold << lock.c_str() << " : '" << lock.Tag() << '\'' << kNormal << kNL;
	}

	out << "Access :" << kNL;

	std::vector<CLogStr>::const_iterator a;
	for(a = rcs.AccessList().begin(); a != rcs.AccessList().end(); ++a)
	{
		out << "\t'" << kBold << *a << kNormal << '\'' << kNL;
	}

	if( !rcs.SymbolicList().empty() )
	{
		out << "Symbolic names :" << kNL;

		std::vector<CRevNumber>::const_iterator n;
		for(n = rcs.SymbolicList().begin(); n != rcs.SymbolicList().end(); ++n)
		{
			const CRevNumber & symb = *n;
			out << '\t' << kBold << symb.c_str() << " : '" << symb.Tag() << '\'' << kNormal << kNL;
		}
	}

	out << "Keyword substitution : '" << kBold << rcs.KeywordSubst() << kNormal << '\'' << kNL;
	out << "Total revisions : " << kBold << rcs.TotRevisions() << kNormal << kNL;
	out << "Selected revisions : " << kBold << rcs.SelRevisions() << kNormal << kNL;
	out << "Description :" << kNL << kBrown << rcs.DescLog() << kNormal << kNL;

	if( recurse )  
	{
		std::vector<CRevFile>::const_iterator i;
		for(i = rcs.AllRevs().begin(); i != rcs.AllRevs().end(); ++i)
		{
			print(out, *i);
		}
	}

	out << kGreen << "===============================================" << kNormal << kNL;
}

/*!
	Print the log node information
	\param out Console
	\param node Log node
	\param offset Offset string
*/
static void print(CColorConsole& out, const CLogNode* node, CStr& offset)
{
	out << offset;
	switch(node->GetType())
	{
	case kNodeHeader :
		{
			CLogNodeHeader *header = (CLogNodeHeader*)node;
			out << offset << "Rcs file : '" << kRed << (**header).RcsFile() << kNormal << '\'' << kNL;
		}
		break;
	case kNodeBranch :
		break;
	case kNodeRev :
		{
			CLogNodeRev *rev = (CLogNodeRev*)node;
			out << offset << kBold << (**rev).RevNum().c_str() << kNormal << kNL;
		}
		break;
	case kNodeTag :
		break;
	}

	CStr newOffset;
	newOffset = offset;
	newOffset << "    ";

	std::vector<CLogNode *>::const_iterator i;
	for(i = node->Childs().begin(); i != node->Childs().end(); ++i)
	{
		print(out, *i, newOffset);
		newOffset << ' ';
	}

	if( node->Next() != 0L )
	{
		print(out, node->Next(), offset);
	}
}

/*!
	Print the file node string
	\param out Console
	\param prefix Node information prefix
	\param nodeStr Node string
	\param separator true to print the separator line, false otherwise
*/
static void print(CColorConsole& out, const char* prefix, const char* nodeStr, bool separator = true)
{
	if( separator )
	{
		out << kBlue << "----------------------------" << kNormal << kNL;
	}

	out << prefix << " : " << kBold << nodeStr << kNormal << kNL;
}

/*!
	Print a syntax colored of the cvs log node
	\param out Console
	\param node Log node
*/
void CvsLogOutput(CColorConsole& out, CLogNode* node)
{
	switch(node->GetType())
	{
	case kNodeHeader:
		{
			CRcsFile& rcs = **(CLogNodeHeader*)node;
			print(out, rcs, false);
		}
		break;
	case kNodeBranch:
		{
			CLogStr& str = **(CLogNodeBranch*)node;
			print(out, "Branch", str);
		}
		break;
	case kNodeRev:
		{
			CRevFile& rev = **(CLogNodeRev*)node;
			print(out, rev);
		}
		break;
	case kNodeTag:
		{
			CLogStr& str = **(CLogNodeTag*)node;
			print(out, "Tag", str);
		}
		break;
	}
}

/*!
	Launch a CVS log command, parse the output, output a graph or a syntax colored of the cvs log output
	\param dir Directory
	\param args Arguments
	\param outGraph true if the output is for graph, false if for the log command
	\param defWnd Default window
*/
void CvsLogParse(const char* dir, const CvsArgs& args, const bool outGraph /*= false*/, void* defWnd /*= 0L*/)
{
	// Make a stream of the cvs log output
	CStr tmpFile;
	FILE* output = launchCVS(dir, args, tmpFile);
	if( output == 0L )
		return;

	// Parse
	std::vector<CRcsFile>& allRcs = CvsLogParse(output);

	// Maybe something went wrong or may be the user
	// asked to print only the RCS names, so print the
	// output directly without parsing../
	if( allRcs.empty() )
	{
		char* line = 0L;
		size_t line_chars_allocated = 0;
		int line_length = 0;

		fseek(output, SEEK_SET, 0);
		
		while( (line_length = getline(&line, &line_chars_allocated, output)) > 0 )
		{
			cvs_outstr(line, line_length);
			cvs_outstr("\n", 1);
		}

		if( line_length < 0 && !feof(output) )
			cvs_err("Cannot read output file (error %d)\n", errno);

		if( line != 0L )
			free(line);
	}

	// Close the temp file
	fclose(output);
	
	if( unlink(tmpFile) != 0 )
	{
		cvs_err("Impossible to delete the file '%s' (error %d)", (const char*)tmpFile, errno);
	}

	if( allRcs.empty() )
		return;

	// Output the result
	CColorConsole out;

	std::vector<CRcsFile>::iterator i;
	for(i = allRcs.begin(); i != allRcs.end(); ++i)
	{
		CRcsFile& rcs = *i;
		
		if( !outGraph )
		{
			print(out, rcs);
		}
		else
		{
			CLogNode* node = CvsLogGraph(rcs);
			if( node == 0L )
			{
				cvs_err("Impossible to create a graph for %s\n", (const char*)rcs.WorkingFile());
			}

#ifdef WIN32
			CWincvsApp* app = (CWincvsApp*)AfxGetApp();
			CGraphDoc* doc;

			if( defWnd != 0L )
				doc = (CGraphDoc*)defWnd;
			else
				doc = (CGraphDoc*)app->GetLogGraph()->OpenDocumentFile(NULL);

			doc->SetNode(node, dir);
#elif qMacCvsPP
			if( defWnd != 0L )
				((CTreeView*)defWnd)->SetNode(node, dir);
			else
				CMacCvsApp::gApp->NewLogTree(node, dir);
#elif qUnix
			UCvsGraph* graph = new UCvsGraph(node, dir);
			void* win = UCreate_GraphWindow();

			UEventSendMessage(graph->GetWidID(), EV_INIT_WIDGET, kUMainWidget, win);
			UEventSendMessage(graph->GetWidID(), EV_SHOW_WIDGET, UMAKEINT(kUMainWidget, 1), 0L);
#else
			CStr offset;
			print(out, node, offset);
			delete node;
#endif
		}
	}

	// Reset the parser
	CvsLogReset();
}
