/*	cvsnt xml xdiff
    Copyright (C) 2004-5 Tony Hoyle and March-Hare Software Ltd

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License version 2.1 as published by the Free Software Foundation.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include <config.h>
#include <system.h>
#include <stdarg.h>
#include <stack>
#include "xdiff.h"
#include "../cvsapi/cvsapi.h"
#include "../diff/diffrun.h"
#include <cvstools.h>
#include "../version.h"

static int (*g_outputFn)(const char *text,size_t len);

struct diffStruct_t
{
	char type;
	size_t start1,end1;
	size_t start2,end2;
	int origStart;
	int size;
};

static int compareTree(CXmlNode *tree1, CXmlNode *tree2,std::vector<std::pair<CXmlNode*,bool> >& changed);
static const char *getPath(const CXmlNode *node, const char *prefix);
static bool nodeEqual(const CXmlNode* node1, const CXmlNode* node2);
static bool walk_tree(CXmlNode *left, CXmlNode *right, int oldline, int& oldmin, int& oldmax, int& newmin, int& newmax);
static bool transform_line_number(CXmlNode *left, CXmlNode *right, size_t& line);
static bool transform_context(CXmlNode *left, CXmlNode *right, const char *line, std::vector<diffStruct_t>& diffArray, diffStruct_t& change);
static bool transform_diff(CXmlNode *left, CXmlNode *right, const char *diff_in, std::vector<diffStruct_t>& diffArray);

static int xdiff_print(const char *fmt, ...)
{
	cvs::string str;
	va_list va;

	va_start(va, fmt);
	cvs::vsprintf(str,256,fmt,va);
	va_end(va);
	g_outputFn(str.c_str(),str.length());
	return str.length();
}

int xdiff_function(const char *name, const char *file1, const char *file2, const char *label1, const char *label2, int argc, const char *const*argv, int (*output_fn)(const char *,size_t))
{
	std::vector<std::pair<CXmlNode*,bool> > changed;
	std::vector<cvs::string> ignore_tag;
	g_outputFn = output_fn;
	bool standard_diff = false;
	CXmlTree tree;

	CTokenLine tok(argc,argv);
	size_t argnum=0;
	CGetOptions opt(tok,argnum,"i:d");
	if(argnum!=argc)
	{
		xdiff_print("Invalid arguments to xdiff.  Aborting.");
		return 1;
	}
	
	for(argnum=0; argnum<opt.count(); argnum++)
	{
		switch(opt[argnum].option)
		{
		case 'i':
			ignore_tag.push_back(opt[argnum].arg);
			break;
		case 'd':
			standard_diff = true;
			break;
		default:
			break;
		}
	}

	FILE *fp1 = fopen(file1,"r");
	if(!fp1)
	{
		xdiff_print("Couldn't open file '%s' (error %d)\n",file1,errno);
		return 1;
	}
	FILE *fp2 = fopen(file2,"r");
	if(!fp2)
	{
		xdiff_print("Couldn't open file '%s' (error %d)\n",file2,errno);
		fclose(fp1);
		return 1;
	}
	cvs::smartptr<CXmlNode> file1Root = tree.ReadXmlFile(fp1,ignore_tag);
	cvs::smartptr<CXmlNode> file2Root = tree.ReadXmlFile(fp2,ignore_tag);
	fclose(fp1);
	fclose(fp2);
	if(!file1Root || !file2Root)
	{
		xdiff_print("Couldn't read file '%s' as valid XML",name);
		return 1;
	}

	if(standard_diff)
	{
//#ifdef _DEBUG
//		cvs::string tempfile1 = "d:\\t\\xml1.out";
//		cvs::string tempfile2 = "d:\\t\\xml2.out";
//		cvs::string difftemp1 = "d:\\t\\diff1.out";
//#else
		cvs::string tempfile1 = CFileAccess::tempfilename("xdiff");
		cvs::string tempfile2 = CFileAccess::tempfilename("xdiff");
		cvs::string difftemp1 = CFileAccess::tempfilename("xdiff");
//#endif
		FILE *f=fopen(tempfile1.c_str(),"w");
		file1Root->WriteXmlFile(f);
		fclose(f);
		f=fopen(tempfile2.c_str(),"w");
		file2Root->WriteXmlFile(f);
		fclose(f);
		CTokenLine diffargs;
		diffargs.addArg("xmldiff");
		diffargs.addArg("-bBNw");
		diffargs.addArg(tempfile1.c_str());
		diffargs.addArg(tempfile2.c_str());
		int ret = diff_run(diffargs.size(),(char**)diffargs.toArgv(),difftemp1.c_str(),NULL);
//#ifndef _DEBUG
		CFileAccess::remove(tempfile1.c_str());
//#endif
		fp2=fopen(tempfile2.c_str(),"r");
		if(!fp2)
		{
			xdiff_print("Internal error - couldn't read partial result file");
//#ifndef _DEBUG
			CFileAccess::remove(tempfile2.c_str());
			CFileAccess::remove(difftemp1.c_str());
//#endif
			return 1;
		}
		file1Root = NULL;
		cvs::smartptr<CXmlNode> temp2Root = tree.ReadXmlFile(fp2,ignore_tag);
		fclose(fp2);

		std::vector<diffStruct_t> diffArray;
		transform_diff(temp2Root,file2Root,difftemp1.c_str(),diffArray);
		temp2Root=NULL;
		file2Root=NULL;
//#ifndef _DEBUG
		CFileAccess::remove(tempfile2.c_str());
		CFileAccess::remove(difftemp1.c_str());
//#endif
		for(size_t n=0; n<diffArray.size(); n++)
		{
			xdiff_print("%c %d %d -> %d %d\n",diffArray[n].type,diffArray[n].start1,diffArray[n].end1,diffArray[n].start2,diffArray[n].end2);
		}
		return ret;
	}
	else
	{
		compareTree(file1Root,file2Root,changed);

		for(size_t i=0; i<changed.size(); i++)
		{
			if(i+1<changed.size() && changed[i].second!=changed[i+1].second && nodeEqual(changed[i].first,changed[i+1].first))
				changed.erase(&changed[i+1]);
			else
				xdiff_print("%s",getPath(changed[i].first,changed[i].second?"- ":"+ "));
		}
		return changed.size()?1:0;
	}
}

static int compareTree(CXmlNode *tree1, CXmlNode *tree2,std::vector<std::pair<CXmlNode*,bool> >& changed)
{
	CXmlNode::ChildArray_t::iterator i = tree1->begin();
	CXmlNode::ChildArray_t::iterator j = tree2->begin();

	while(i!=tree1->end() && j!=tree2->end())
	{
		CXmlNode* c1 = *i;
		CXmlNode* c2 = *j;
		int diff = strcmp(c1->GetName(),c2->GetName());
		if(diff<0)
		{
			changed.push_back(std::pair<CXmlNode*,bool>(c1,true));
			++i;
		}
		else if(diff>0)
		{
			changed.push_back(std::pair<CXmlNode*,bool>(c2,false));
			++j;
		}
		else
		{
			diff = strcmp(c1->GetValue(),c2->GetValue());
			if(diff<0)
			{
				changed.push_back(std::pair<CXmlNode*,bool>(c1,true));
				++i;
			}
			else if(diff>0)
			{
				changed.push_back(std::pair<CXmlNode*,bool>(c2,false));
				++j;
			}
			else
			{
				compareTree(c1,c2,changed);
				++i;
				++j;
			}
		}
	}		
	while(i!=tree1->end())
	{
		CXmlNode* c1 = *i;
		changed.push_back(std::pair<CXmlNode*,bool>(c1,true));
		++i;
	}
	while(j!=tree2->end())
	{
		CXmlNode* c2 = *j;
		changed.push_back(std::pair<CXmlNode*,bool>(c2,false));
		++j;
	}
	return 0;
}

static const char *getPath(const CXmlNode *node, const char *prefix)
{
	std::stack<const CXmlNode*> nodes;
	static cvs::string path;
	int lev;
	char tmp[64];

	do
	{
		nodes.push(node);
		node = node->getParent();
	} while(node);

	path="";

	lev=0;
	while(nodes.size())
	{
		node = nodes.top();
		snprintf(tmp,10,"%5d",node->getStartLine());
		path+=tmp;
		path+=": ";
		path+=prefix;
		path.append(lev,' ');
		path.append("<");
		path+= node->GetName();
		path.append(">");
		nodes.pop();
		if(nodes.size())
			path+= "\n";
		lev++;
	}
	path+=node->GetValue();
	--lev;
	path.append("</");
	path+=node->GetName();
	path.append(">");
	path+="\n";
	while((node=node->getParent())!=NULL)
	{
		--lev;
		snprintf(tmp,10,"%5d",node->getEndLine());
		path+=tmp;
		path+=": ";
		path+=prefix;
		path.append(lev,' ');
		path.append("</");
		path+=node->GetName();
		path.append(">");
		path+="\n";
	}
	return path.c_str();
}

static bool nodeEqual(const CXmlNode* node1, const CXmlNode* node2)
{
	if((!node1 && node2) || (node2 && !node1))
		return false;
	if(!node1 && !node2)
		return true;

	if(strcmp(node1->GetName(),node2->GetName()))
		return false;
	if(strcmp(node1->GetValue(),node2->GetValue()))
		return false;
	return nodeEqual(node1->getParent(),node2->getParent());
}

/* Walk two semantically identical trees and find map source line numbers */
static bool walk_tree(CXmlNode *left, CXmlNode *right, int oldline, int& oldmin, int& oldmax, int& newmin, int& newmax)
{
	CXmlNode::ChildArray_t::iterator i = left->begin();
	CXmlNode::ChildArray_t::iterator j = right->begin();

	while(i!=left->end() && j!=right->end())
	{
		CXmlNode* c1 = *i;
		CXmlNode* c2 = *j;
		if(strcmp(c1->GetName(),c2->GetName()) || c1->size()!=c2->size())
		{
			xdiff_print("Internal error - trees not identical");
		}
		if(strcmp(c1->GetValue(),c2->GetValue()))
		{
//			printf("value different:\n");
//			printf("%s\n-------%s\n",c1->GetValue(),c2->GetValue());
		}
		if(c1->getStartLine()>=oldmin && c1->getEndLine()<=oldmax && c1->getStartLine()<=oldline && c1->getEndLine()>=oldline)
		{
			oldmin=c1->getStartLine();
			oldmax=c1->getEndLine();
			newmin=c2->getStartLine();
			newmax=c2->getEndLine();
		}	
		walk_tree(c1,c2,oldline,oldmin,oldmax,newmin,newmax);
		++i;
		++j;
	}		
	return 0;
}

static bool transform_line_number(CXmlNode *left, CXmlNode *right, size_t& line)
{
	int oldmin,oldmax,newmin,newmax;

	oldmin=-1;
	oldmax=0x7fffffff;

	walk_tree(left,right,line,oldmin,oldmax,newmin,newmax);
	line=(line-oldmin)+newmin;
	return true;
}

static bool transform_context(CXmlNode *left, CXmlNode *right, const char *line, std::vector<diffStruct_t>& diffArray, diffStruct_t& change)
{
	const char *p;
	/* Line is <start>[,<end>]<type><start>[,end] */

	memset(&change,0,sizeof(change));

	p=line;
	while(isdigit(*p)) p++;
	change.start1=atoi(line);
	if(*p==',')
	{
		line=++p;
		while(isdigit(*p)) p++;
		change.end1=atoi(line);
	}
	change.type=*p;
	line=++p;
	while(isdigit(*p)) p++;
	change.start2=atoi(line);
	if(*p==',')
	{
		line=++p;
		while(isdigit(*p)) p++;
		change.end2=atoi(line);
	}

	change.origStart=change.start2;
/*
	for(size_t n=0; n<diffArray.size(); n++)
	{
		if(change.start2>=diffArray[n].origStart)
			change.start2+=diffArray[n].size;
		if(change.end2>=diffArray[n].origStart)
			change.end2+=diffArray[n].size;
	}
*/
	transform_line_number(left,right,change.start1);
	if(change.end1)
		transform_line_number(left,right,change.end1);
	transform_line_number(left,right,change.start2);
	if(change.end2)
		transform_line_number(left,right,change.end2);
/*
	for(size_t n=0; n<diffArray.size(); n++)
	{
		if(change.start2>=diffArray[n].start2)
			change.start2-=diffArray[n].size;
		if(change.end2>=diffArray[n].start2)
			change.end2-=diffArray[n].size;
	}
*/
	return true;
}

static bool transform_diff(CXmlNode *left, CXmlNode *right, const char *diff_in, std::vector<diffStruct_t>& diffArray)
{
	CFileAccess in;
	CFileAccess out;
	size_t n;

	if(!in.open(diff_in,"r"))
		return false;

	cvs::string line;

	while(in.getline(line))
	{
		diffStruct_t change;
		transform_context(left,right,line.c_str(),diffArray,change);
		switch(change.type)
		{
		case 'd':
			n=0;
			in.getline(line);
			if(change.end1)
				for(; n<(change.end1-change.start1) && in.getline(line); n++)
					;
			change.size=-(int)(n+1);
			break;
		case 'a':
			n=0;
			in.getline(line);
			if(change.end2)
				for(; n<(change.end2-change.start2) && in.getline(line); n++)
					;
			change.size=n+1;
			break;
		case 'c':
			n=0;
			in.getline(line);
			if(change.end1)
				for(; n<(change.end1-change.start1) && in.getline(line); n++)
					;
			change.size=-(int)(n+1);
			in.getline(line); // Separator
			n=0;
			in.getline(line);
			if(change.end2)
				for(; n<(change.end2-change.start2) && in.getline(line); n++)
					;
			change.size+=n+1;
			break;
		default:
			xdiff_print("Unknown change type '%c'?",change.type);
			break;
		}
		diffArray.push_back(change);
	}
	return true;
}

static int init(const struct plugin_interface *plugin);
static int destroy(const struct plugin_interface *plugin);
static void *get_interface(const struct plugin_interface *plugin, unsigned interface_type, void *param);

static xdiff_interface xml_xdiff =
{
	{
		PLUGIN_INTERFACE_VERSION,
		"XML XDiff handler",CVSNT_PRODUCTVERSION_STRING,"XmlXDiff",
		init,
		destroy,
		get_interface,
		NULL /* Configure */
	},
	xdiff_function
};

static int init(const struct plugin_interface *plugin)
{
	return 0;
}

static int destroy(const struct plugin_interface *plugin)
{
	return 0;
}

static void *get_interface(const struct plugin_interface *plugin, unsigned interface_type, void *param)
{
	if(interface_type!=pitXdiff)
		return NULL;

	return (void*)&xml_xdiff;
}

plugin_interface *get_plugin_interface()
{
	return (plugin_interface*)&xml_xdiff;
}



syntax highlighted by Code2HTML, v. 0.9.1