/*
** 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.
*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <new>

#include "CvsLog.h"
#include "common.h"

#ifndef NEW
#define NEW new
#endif

std::vector<CRcsFile> gRcsFiles;
CRcsFile *gRcsFile = 0L;
CRevFile *gRevFile = 0L;
#ifdef TARGET_OS_MAC
std::vector<void *> gMacAlloca;
#endif

//
// CRevNumber
//

void CRevNumber::reset(void)
{
	allDigits.erase(allDigits.begin(), allDigits.end());
	tagName = "";
}

CRevNumber & CRevNumber::operator+=(int adigit)
{
	allDigits.push_back(adigit);
	return *this;
}

CRevNumber & CRevNumber::operator=(const CRevNumber & arev)
{
	reset();
	for(int i = 0; i < arev.size(); i++)
	{
		*this += arev[i];
	}
	tagName = arev.Tag();
	return *this;
}

int CRevNumber::operator[](int index) const
{
	if(index < 0 || index >= size())
		return -1;
	return allDigits[index];
}

const char *CRevNumber::c_str(void) const
{
	static CLogStr str;
	str = "";
	if(allDigits.empty())
		return str;
	std::vector<int>::const_iterator i = allDigits.begin();
	str << *i;
	++i;
	for(; i != allDigits.end(); ++i)
	{
		str << '.' << *i;
	}
	return str;
}

int CRevNumber::cmp(const CRevNumber & arev) const
{
	const std::vector<int> & rev1 = IntList();
	const std::vector<int> & rev2 = arev.IntList();

	std::vector<int>::const_iterator i = rev1.begin();
	std::vector<int>::const_iterator j = rev2.begin();
	while(i != rev1.end() && j != rev2.end())
	{
		if((*i) != (*j))
			return (*i) < (*j) ? -1 : 1;
		++i;
		++j;
	}
	if(i == rev1.end() && j != rev2.end())
		return -1;
	if(i != rev1.end() && j == rev2.end())
		return 1;
	return 0;
}

bool CRevNumber::operator==(const CRevNumber & arev) const
{
	if(size() != arev.size() || size() == 0)
		return false;

	const std::vector<int> & rev1 = IntList();
	const std::vector<int> & rev2 = arev.IntList();
	std::vector<int>::const_iterator i = rev1.begin();
	std::vector<int>::const_iterator j = rev2.begin();
	return memcmp(&*i, &*j, size() * sizeof(int)) == 0;
}

bool CRevNumber::ischildof(const CRevNumber & arev) const
{
	if((arev.size() + 2) != size())
		return false;

	if(size() == 0 || arev.size() == 0)
		return false;

	const std::vector<int> & rev1 = IntList();
	const std::vector<int> & rev2 = arev.IntList();
	std::vector<int>::const_iterator i = rev1.begin();
	std::vector<int>::const_iterator j = rev2.begin();
	return memcmp(&*i, &*j, arev.size() * sizeof(int)) == 0;
}

bool CRevNumber::issamebranch(const CRevNumber & arev) const
{
	if(size() != arev.size() || size() == 0)
		return false;
	const std::vector<int> & rev1 = IntList();
	const std::vector<int> & rev2 = arev.IntList();
	std::vector<int>::const_iterator i = rev1.begin();
	std::vector<int>::const_iterator j = rev2.begin();
	return memcmp(&*i, &*j, (size() - 1) * sizeof(int)) == 0;
}

bool CRevNumber::ispartof(const CRevNumber & arev) const
{
	const std::vector<int> & rev1 = IntList();
	const std::vector<int> & rev2 = arev.IntList();
	std::vector<int>::const_iterator i = rev1.begin();
	std::vector<int>::const_iterator j = rev2.begin();

	if(size() == 0 || arev.size() == 0)
		return false;

	if((arev.size() & 1) != 0)
	{
		// special case for "1.1.1"
		if((arev.size() + 1) != size())
			return false;
		return memcmp(&*i, &*j, arev.size() * sizeof(int)) == 0;
	}

	// case for 1.1.1.1.2.4 is part of 1.1.1.1.0.2
	if(arev.size() != size() || size() < 2)
		return false;

	if(memcmp(&*i, &*j, (size() - 2) * sizeof(int)) != 0)
		return false;

	return arev.IntList()[size() - 2] == 0 &&
		IntList()[size() - 2] == arev.IntList()[size() - 1];
}

bool CRevNumber::issubbranchof(const CRevNumber & arev) const
{
	const std::vector<int> & rev1 = IntList();
	const std::vector<int> & rev2 = arev.IntList();
	std::vector<int>::const_iterator i = rev1.begin();
	std::vector<int>::const_iterator j = rev2.begin();

	if(size() == 0 || arev.size() == 0)
		return false;

	if((size() & 1) != 0)
	{
		// special case for "1.1.1"
		if((arev.size() + 1) != size())
			return false;
		return memcmp(&*i, &*j, arev.size() * sizeof(int)) == 0;
	}

	// case for 1.4.0.2 is subbranch of 1.4
	if((arev.size() + 2) != size() || IntList()[arev.size()] != 0)
		return false;

	return memcmp(&*i, &*j, arev.size() * sizeof(int)) == 0;
}

//
// CLogStr
//

void CLogStr::flush(void)
{
	if(str == 0L)
		return;
	free(str);
	str = 0L;
}

const char *CLogStr::operator=(const char *newstr)
{
	flush();
	if(newstr == 0L)
		return 0L;

	int l = strlen(newstr);
	str = (char *)malloc((l + 1) * sizeof(char));
	if(str == 0L)
		throw std::bad_alloc();

	strcpy(str, newstr);

	return *this;
}

const CLogStr & CLogStr::operator=(const CLogStr & newstr)
{
	flush();
	*this = (const char *)newstr;
	return *this;
}

const CLogStr & CLogStr::set(const char *buf, unsigned int len)
{
	flush();
	if(len == 0)
		return *this;

	str = (char *)malloc((len + 1) * sizeof(char));
	if(str == 0L)
		throw std::bad_alloc();

	memcpy(str, buf, len * sizeof(char));
	str[len] = '\0';

	return *this;
}

const CLogStr & CLogStr::replace(char which, char bywhich)
{
	if(str == 0L)
		return *this;
	
	char *buf = str;
	while(*buf)
	{
		if(*buf == which)
			*buf++ = bywhich;
		else
			buf++;
	}
	return *this;
}

CLogStr & CLogStr::operator<<(const char *addToStr)
{
	if(addToStr == 0L)
		return *this;

	if(str == 0L)
	{
		*this = addToStr;
		return *this;
	}

	unsigned int len = strlen(addToStr);
	if(len == 0)
		return *this;

	unsigned curlen = length();
	str = (char *)realloc(str, (len + curlen + 1) * sizeof(char));
	if(str == 0L)
		throw std::bad_alloc();

	memcpy(str + curlen, addToStr, len * sizeof(char));
	str[len + curlen] = '\0';

	return *this;
}

CLogStr & CLogStr::operator<<(char addToStr)
{
	char astr[2] = {'\0', '\0'};
	astr[0] = addToStr;
	return *this << astr;
}

CLogStr & CLogStr::operator<<(int addToStr)
{
	char astr[50];
	sprintf(astr, "%d", addToStr);
	return *this << astr;
}

//
// CRcsFile
//

CRcsFile::CRcsFile()
{
	selRevisions = 0;
	totRevisions = 0;
	lockStrict = false;
}

CRcsFile::CRcsFile(const CRcsFile & afile)
{
	*this = afile;
}

CRcsFile::~CRcsFile()
{
}

CRcsFile & CRcsFile::operator=(const CRcsFile & afile)
{
	rcsFile = afile.rcsFile;
	workingFile = afile.workingFile;
	headRev = afile.headRev;
	branchRev = afile.branchRev;
	keywordSubst = afile.keywordSubst;
	accessList = afile.accessList;
	symbolicList = afile.symbolicList;
	locksList = afile.locksList;
	selRevisions = afile.selRevisions;
	totRevisions = afile.totRevisions;
	lockStrict = afile.lockStrict;
	allRevs = afile.allRevs;
	descLog = afile.descLog;
	return *this;
}

#ifdef qUnix
void CRcsFile::print(OSTREAM & out) const
{
	out << "Rcs file : '" << RcsFile() << "'\n";
	out << "Working file : '" << WorkingFile() << "'\n";
	out << "Head revision : " << HeadRev().c_str() << '\n';
	out << "Branch revision : " << BranchRev().c_str() << '\n';

	out << "Locks :" << (LockStrict() ? " strict" : "") << '\n';
	std::vector<CRevNumber>::const_iterator s;
	for(s = LocksList().begin(); s != LocksList().end(); ++s)
	{
		const CRevNumber & lock = *s;
		out << '\t' << lock.c_str() << " : '" << lock.Tag() << "'\n";
	}

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

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

	out << "Keyword substitution : '" << KeywordSubst() << "'\n";
	out << "Total revisions : " << TotRevisions() << "\n";
	out << "Selected revisions : " << SelRevisions() << "\n";
	out << "Description :\n" << DescLog() << '\n';

	std::vector<CRevFile>::const_iterator i;
	for(i = AllRevs().begin(); i != AllRevs().end(); ++i)
	{
		i->print(out);
	}
}
#endif

extern "C" {
	static int sortRevs(const void *r1, const void *r2);
}

#ifdef WIN32
extern "C"
#endif /* WIN32 */
static int sortRevs(const void *r1, const void *r2)
{
	CRevNumber & rev1 = ((CRevFile *)r1)->RevNum();
	CRevNumber & rev2 = ((CRevFile *)r2)->RevNum();
	return rev1.cmp(rev2);
}

void CRcsFile::sort()
{
#if !defined(__MWERKS__) && (__GNUC__ < 3) && _MSC_VER < 0x514
	qsort(AllRevs().begin(), AllRevs().size(), sizeof(CRevFile), sortRevs);
#else /* __MWERKS__ || __GNUC__ > 2 */
	qsort(&*AllRevs().begin(), AllRevs().size(), sizeof(CRevFile), sortRevs);
#endif /* __MWERKS__ || __GNUCC__ > 2 */
}

//
// CRevFile
//

CRevFile::CRevFile()
{
	chgPos = 0;
	chgNeg = 0;
	memset(&revTime, 0, sizeof(revTime));
}

CRevFile::CRevFile(const CRevFile & afile)
{
	*this = afile;
}

CRevFile::~CRevFile()
{
}

CRevFile & CRevFile::operator=(const CRevFile & afile)
{
	revNum = afile.revNum;
	revTime = afile.revTime;
	locker = afile.locker;
	branchesList = afile.branchesList;
	author = afile.author;
	state = afile.state;
	chgPos = afile.chgPos;
	chgNeg = afile.chgNeg;
	descLog = afile.descLog;
	keywordSubst = afile.keywordSubst;
	commitID = afile.commitID;
	mergePoint = afile.mergePoint;

	return *this;
}

#ifdef qUnix
void CRevFile::print(OSTREAM & out) const
{
	out << "----------------------------\n";
	out << "Revision : " << RevNum().c_str() << '\n';
	if(!Locker().empty())
		out << "Locked by : '" << Locker() << "'\n";
	out << "Date : " <<
		(RevTime().tm_year + 1900) << '/' <<
		RevTime().tm_mon << '/' <<
		RevTime().tm_mday << ' ' <<
		RevTime().tm_hour << ':' <<
		RevTime().tm_min << ':' <<
		RevTime().tm_sec << '\n';
	out << "Author : '" << Author() << "'\n";
	out << "State : '" << State() << "'\n";
	out << "Lines : +" << ChgPos() << ' ' << ChgNeg() << '\n';

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

	if(!MergePoint().empty())
		out << "MergePoint : '" << MergePoint().c_str() << "'\n";

	out << "Description :\n" << DescLog() << '\n';
}
#endif

std::vector<CRcsFile> & CvsLogParse(FILE * file)
{
#if qCvsDebug
	yydebug = 0;
#endif
	yy_flex_debug = 0;
	yyin = file;
	yyreset();
	yyrestart(yyin);
	yyparse();

#ifdef TARGET_OS_MAC
	std::vector<void *>::iterator i;
	for(i = gMacAlloca.begin(); i != gMacAlloca.end(); i++)
	{
		if(*i != 0L)
			free(*i);
		*i = 0L;
	}
	gMacAlloca.erase(gMacAlloca.begin(), gMacAlloca.end());
#endif /* TARGET_OS_MAC */

	return gRcsFiles;
}

void CvsLogReset(void)
{
	yyreset();
}

//
// tree nodes
//

CLogNode::CLogNode(CLogNode * r)
{
	root = r;
	next = 0L;
	user = 0L;
}

CLogNode::~CLogNode()
{
	if(Next() != 0L)
		delete Next();
	std::vector<CLogNode *>::iterator i;
	for(i = Childs().begin(); i != Childs().end(); ++i)
	{
		delete *i;
		*i = 0L;
	}
	if(user != 0L)
		delete user;
}

static bool insertSymbName(CLogNode *node, const CRevNumber & symb)
{
	if(node->GetType() == kNodeRev)
	{
		CLogNodeRev & rev = *(CLogNodeRev *)node;
		if((*rev).RevNum() == symb)
		{
			// insert the tag as achild of the node
			CLogNodeTag *tag = NEW CLogNodeTag(symb.Tag(), node);
			node->Childs().push_back(tag);
			return true;
		}
	}

	std::vector<CLogNode *>::iterator i;
	for(i = node->Childs().begin(); i != node->Childs().end(); ++i)
	{
		if(insertSymbName(*i, symb))
			return true;
	}
	if(node->Next() != 0L && insertSymbName(node->Next(), symb))
		return true;

	return false;
}

static bool insertBranchName(CLogNode *node, const CRevNumber & symb)
{
	std::vector<CLogNode *>::iterator i;
	for(i = node->Childs().begin(); i != node->Childs().end(); ++i)
	{
		CLogNode *subnode = *i;
		if(subnode->GetType() == kNodeRev)
		{
			CLogNodeRev & rev = *(CLogNodeRev *)subnode;
			if((*rev).RevNum().ispartof(symb))
			{
				// insert the branch name as previous node of the
				// first node of that branch
				CLogNodeBranch *branch = NEW CLogNodeBranch(symb.Tag(), node);
				branch->Next() = subnode;
				*i = branch;
				return true;
			}
		}
		if(insertBranchName(subnode, symb))
			return true;
	}
	if(node->Next() != 0L && insertBranchName(node->Next(), symb))
		return true;

	// we didn't find to connect this branch because there is no
	// revision in this branch (empty branch) : so add it as a child of this node.
	if(node->GetType() == kNodeRev)
	{
		CLogNodeRev & rev = *(CLogNodeRev *)node;
		if(symb.issubbranchof((*rev).RevNum()))
		{
			CLogNodeBranch *branch = NEW CLogNodeBranch(symb.Tag(), node);
			node->Childs().push_back(branch);
		}
	}

	return false;
}

CLogNode *CvsLogGraph(CRcsFile & rcsfile)
{
	// we sort the revisions in order to build the tree
	// using a stack-algorithm
	rcsfile.sort();

	CLogNodeHeader * header = NEW CLogNodeHeader(rcsfile);
	CLogNodeRev * curNode = 0L;

	// append the revisions to the tree
	std::vector<CRevFile>::const_iterator i;
	for(i = rcsfile.AllRevs().begin(); i != rcsfile.AllRevs().end(); ++i)
	{
		const CRevFile & rev = *i;
		
		if(curNode == 0L)
		{
			CLogNodeRev *nrev = NEW CLogNodeRev(rev, header);
			header->Childs().push_back(nrev);
			curNode = nrev;
			continue;
		}

		do
		{
			const CRevNumber & curRev = (**curNode).RevNum();
			const CRevNumber & thisRev = rev.RevNum();

			if(thisRev.ischildof(curRev))
			{
				CLogNodeRev *nrev = NEW CLogNodeRev(rev, curNode);
				curNode->Childs().push_back(nrev);
				curNode = nrev;
				break;
			}
			else if(thisRev.issamebranch(curRev))
			{
				CLogNodeRev *nrev = NEW CLogNodeRev(rev, curNode);
				curNode->Next() = nrev;
				curNode = nrev;
				break;
			}
			if(curNode->Root() == header)
				curNode = 0L;
			else
				curNode = (CLogNodeRev *)curNode->Root();
		} while(curNode != 0L);

		if(curNode == 0L)
		{
			CLogNodeRev *nrev = NEW CLogNodeRev(rev, header);
			header->Childs().push_back(nrev);
			curNode = nrev;
		}
	}

	// append the tags
	std::vector<CRevNumber>::const_iterator s;
	for(s = rcsfile.SymbolicList().begin(); s != rcsfile.SymbolicList().end(); ++s)
	{
		insertSymbName(header, *s);
	}

	// append the branch names
	for(s = rcsfile.SymbolicList().begin(); s != rcsfile.SymbolicList().end(); ++s)
	{
		insertBranchName(header, *s);
	}

	return header;
}

#ifdef TARGET_OS_MAC
void *cvstree_alloca(unsigned size)
{
	void *res = malloc(size);
	if(res == 0L)
		return 0L;
	
	gMacAlloca.push_back(res);
	return res;
}

char *cvstree_strdup(const char *string)
{
	int size = strlen(string);
	char *result = (char *)malloc((size + 1) * sizeof(char));
	if(result == 0L)
		return 0L;
	strcpy(result, string);
	return result;
}
#endif /* TARGET_OS_MAC */
