/*!\file KMLFileUtils.cpp
 * \brief: utilities for kml file reading.
 */

/*Headers:*/
/*{{{*/
#ifdef HAVE_CONFIG_H
	#include <config.h>
#else
#error "Cannot compile with HAVE_CONFIG_H symbol! run configure first!"
#endif

#include <stdio.h>
#include <string.h>
#include "../objects.h"
#include "../../shared/shared.h"
#include "../../io/io.h"
#include "../../Container/Container.h"
#include "../../include/include.h"
/*}}}*/

/*FUNCTION  KMLFileToken(FILE* fid,int* pncom=NULL,char*** ppcom=NULL) {{{*/
char* KMLFileToken(FILE* fid,
				   int* pncom=NULL,char*** ppcom=NULL){

/*  get the next token (tag or field) in the file  */

	bool    inew=1,itag=0,ifield=0;
	int     c;
	int     ibuf=0,buflen=1024,bufblk=1024;
	char    *buffer=NULL,*bufferc=NULL;

	buffer=(char *) xmalloc(buflen*sizeof(char));
	buffer[0]='\0';

/*  read kml file character-by-character  */

//  note that fgets includes newline
//	fgets(buffer,buflen,fid);

	while ((c=getc(fid)) != EOF) {
		/*  ignore leading blanks  */
		if (inew && isspace(c))
			continue;

		/*  distinguish between tag or field  */
		if (!itag && !ifield) {

			/*  distinguish between tag or comment  */
			if (c == '<') {
				ungetc(c,fid);
				if (!(bufferc=KMLFileTokenComment(fid))) {
					c=getc(fid);
					itag=1;
				}
				else {
					if (pncom && ppcom) {
						(*pncom)++;
						*ppcom=(char **) xrealloc(*ppcom,*pncom*sizeof(char*));
						(*ppcom)[*pncom-1]=bufferc;
					}
					else
						xfree((void**)&bufferc);
					inew=1;
					continue;
				}
			}
			else
				ifield=1;
			inew=0;
			KMLFileTokenBuffer(&buffer,&ibuf,&buflen,
							   c,
							   bufblk);
		}

		/*  accumulate tag, not including newlines  */
		else if (itag) {
			if (c != '\n') {
				inew=0;
				KMLFileTokenBuffer(&buffer,&ibuf,&buflen,
								   c,
								   bufblk);
				if (c == '>')
					break;
			}
			else
				inew=1;
		}

		/*  accumulate field, including newlines  */
		else if (ifield) {
			/*  distinguish between another tag or comment  */
			if (c == '<') {
				ungetc(c,fid);
				if (!(bufferc=KMLFileTokenComment(fid)))
					break;
				else
					if (pncom && ppcom) {
						(*pncom)++;
						*ppcom=(char **) xrealloc(*ppcom,*pncom*sizeof(char*));
						(*ppcom)[*pncom-1]=bufferc;
					}
					else
						xfree((void**)&bufferc);
			}
			else {
				inew=0;
				KMLFileTokenBuffer(&buffer,&ibuf,&buflen,
								   c,
								   bufblk);
				if (c == '\n')
					inew=1;
			}
		}

	}

/*  remove trailing blanks or newline  */

	while (ibuf > 0)
		if (isspace(buffer[ibuf-1]))
			ibuf--;
		else {
			buffer[ibuf]='\0';
			break;
		}

//	if      (itag)
//		_pprintLine_("tag buffer (length=" << ibuf << "):");
//	else if (ifield)
//		_pprintLine_("field buffer (length=" << ibuf << "):");
//	_pprintLine_(buffer);

	if (!ibuf)
		xfree((void**)&buffer);

	return(buffer);
}
/*}}}*/
/*FUNCTION  KMLFileTokenComment(FILE* fid) {{{*/
char* KMLFileTokenComment(FILE* fid){

/*  check for comment in the file and read it  */

	bool    inew=1;
	int     i;
	int     c;
	int     ibuf=0,buflen=1024,bufblk=1024;
	char*   buffer=NULL;

	buffer=(char *) xmalloc(buflen*sizeof(char));
	buffer[0]='\0';

/*  read kml file character-by-character  */

	while ((c=getc(fid)) != EOF) {
		/*  ignore leading blanks  */
		if (inew && isspace(c))
			continue;

		inew=0;
		KMLFileTokenBuffer(&buffer,&ibuf,&buflen,
						   c,
						   bufblk);

		/*  check for comment  */
		if (ibuf <= 4) {
			if ((ibuf == 1 && buffer[0] != '<') ||
				(ibuf == 2 && buffer[1] != '!') ||
				(ibuf == 3 && buffer[2] != '-') ||
				(ibuf == 4 && buffer[3] != '-')) {
				for (i=ibuf-1; i>=0; i--)
					ungetc(buffer[i],fid);
				xfree((void**)&buffer);
				return(buffer);
			}
		}

		/*  accumulate comment, including newlines  */
		else
			if (buffer[ibuf-3]=='-' && buffer[ibuf-2]=='-' && buffer[ibuf-1]=='>')
				break;
	}

/*  remove trailing blanks or newline  */

	while (ibuf > 0)
		if (isspace(buffer[ibuf-1]))
			ibuf--;
		else {
			buffer[ibuf]='\0';
			break;
		}

//	_pprintLine_("comment buffer (length=" << ibuf << "):");
//	_pprintLine_(buffer);

	if (!ibuf)
		xfree((void**)&buffer);

	return(buffer);
}
/*}}}*/
/*FUNCTION  KMLFileTokenBuffer {{{*/
void KMLFileTokenBuffer(char** pbuffer,int* pibuf,int* pbuflen,
						int c,
						int bufblk){

/*  add the specified character to the token buffer  */

	char*   buffer=NULL;

/*  check buffer length and realloc if necessary  */

	if (*pibuf+2 > *pbuflen) {
		*pbuflen+=bufblk;
		*pbuffer=(char *) xrealloc(*pbuffer,*pbuflen*sizeof(char));
	}

/*  add character and terminator  */

	(*pbuffer)[(*pibuf)++]=c;
	(*pbuffer)[ *pibuf   ]='\0';

	return;
}
/*}}}*/
/*FUNCTION  KMLFileTagName {{{*/
char* KMLFileTagName(char* pname,
					 char* ktag){

	return(KMLFileTagName(pname,NULL,0,
						  ktag));
}
/*}}}*/
/*FUNCTION  KMLFileTagName {{{*/
char* KMLFileTagName(char* pname,int *m,int maxlen,
					 char* ktag){

/*  for the given tag buffer, read and store the name  */

	char*   ktagi;
	char*   ktokn;

	if (strncmp(&ktag[0],"<"        ,1) || strncmp(&ktag[strlen(ktag)-1],">",1))
		_error2_("KMLFileTagName -- Missing tag delimiters in " << ktag << ".\n");

/*  strtok modifies ktag, so work on copy  */

	ktagi=(char *) xmalloc((strlen(ktag)+1)*sizeof(char));
	memcpy(ktagi,ktag,(strlen(ktag)+1)*sizeof(char));

/*  skip opening delimeter and find subsequent blank or closing delimiter  */

	ktokn=strtok(ktagi,"< >");
//	_pprintLine_("KMLFileTagName -- initial token=\"" << ktokn << "\".");

	if (!pname) {
		if (maxlen)
			pname=(char *) xmalloc((maxlen       +1)*sizeof(char));
		else
			pname=(char *) xmalloc((strlen(ktokn)+1)*sizeof(char));
	}

	if (maxlen && (maxlen < strlen(ktokn))) {
		_pprintLine_("KMLFileTagName -- string field too short for " << ktag << ".");
		_pprintLine_("KMLFileTagName -- \"" << ktokn << "\" truncated to " << maxlen << " characters.");
		strncpy(pname,ktokn,maxlen);
	}
	else
		memcpy(pname,ktokn,(strlen(ktokn)+1)*sizeof(char));

	xfree((void**)&ktagi);

	if (m)
		*m=strlen(pname);

	return(pname);
}
/*}}}*/
/*FUNCTION  KMLFileTagAttrib {{{*/
int KMLFileTagAttrib(KML_Object* kobj,
					 char* ktag){

/*  for the given tag buffer, read and store the attributes  */

	char*   ktagi;
	char*   ktokn;
	char*   ktokv;
	char    quote[]={'\"','\0'};
	int     isolo=0;

/*  strtok modifies ktag, so work on copy  */

	ktagi=(char *) xmalloc((strlen(ktag)+1)*sizeof(char));
	memcpy(ktagi,ktag,(strlen(ktag)+1)*sizeof(char));

/*  loop through tag to find all attributes  */

	/*  return first non blank and move past subsequent blank  */
	ktokn=strtok(ktagi," ");
//	_pprintLine_("KMLFileTagAttrib -- initial token=\"" << ktokn << "\".");

	/*  return next non " =?/>" and move past subsequent " =?/>"  */
	while (ktokn=strtok(NULL," =?/>")) {

		/*  return next non quote and move past subsequent quote  */
		ktokv=strtok(NULL,quote);
//		_pprintLine_("KMLFileTagAttrib -- attribute " << ktokn << "=\"" << ktokv << "\".");

/*  add the attribute to the dataset  */

		if (kobj)
			kobj->AddAttrib(ktokn,ktokv);
	}

	xfree((void**)&ktagi);

/*  check for xml declaration, dtd declaration, or solo tag  */

	if ((!strncmp(&ktag[0],"<?"       ,2) && !strncmp(&ktag[strlen(ktag)-2],"?>",2)) ||
		(!strncmp(&ktag[0],"<!DOCTYPE",9) && !strncmp(&ktag[strlen(ktag)-1], ">",1)) ||
		(!strncmp(&ktag[0],"<"        ,1) && !strncmp(&ktag[strlen(ktag)-2],"/>",2)))
		isolo=1;
//	_pprintLine_("KMLFileTagAttrib -- isolo=" << isolo << ".");

	return(isolo);
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
int KMLFileTokenParse(int* pival,
					  char* ktag,
					  FILE* fid){

	char*   kstr;

/*  get next token and convert to appropriate format  */

	if (!(kstr=KMLFileToken(fid,
							NULL,NULL)) ||
		(kstr[0] == '<'))
		_error2_("KMLFileTokenParse -- Missing integer field for " << ktag << ".\n");

	sscanf(kstr,"%d",pival);
	xfree((void**)&kstr);

/*  get additional token and compare to closing tag  */

	if (ktag)
		if (!(kstr=KMLFileToken(fid,
								NULL,NULL)) ||
			(kstr[0] != '<') ||
			(kstr[1] != '/') ||
			(strncmp(&(kstr[2]),&(ktag[1]),strlen(ktag)-1)))
		  {_error2_("KMLFileTokenParse -- Missing closing tag for " << ktag << ".\n");}
		else
			xfree((void**)&kstr);

//	_pprintLine_("KMLFileTokenParse -- " << ktag << "=" << *pival << ".");

	return(0);
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
int KMLFileTokenParse(bool* pbval, char* ktag, FILE* fid){

	int     ival;
	char*   kstr;

/*  get next token and convert to appropriate format  */

	if (!(kstr=KMLFileToken(fid,
							NULL,NULL)) ||
		(kstr[0] == '<'))
	  {_error2_("KMLFileTokenParse -- Missing bool field for " << ktag << ".\n");}

	sscanf(kstr,"%d",&ival);
	*pbval=(bool)ival;
	xfree((void**)&kstr);

/*  get additional token and compare to closing tag  */

	if (ktag)
		if (!(kstr=KMLFileToken(fid,
								NULL,NULL)) ||
			(kstr[0] != '<') ||
			(kstr[1] != '/') ||
			(strncmp(&(kstr[2]),&(ktag[1]),strlen(ktag)-1)))
		  {_error2_("KMLFileTokenParse -- Missing closing tag for " << ktag << ".\n");}
		else
			xfree((void**)&kstr);

//	_pprintLine_("KMLFileTokenParse -- " << ktag << "=" << (*pbval ? "true" : "false") << ".");

	return(0);
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
char* KMLFileTokenParse(char* pstr,
						char* ktag,
						FILE* fid){

	return(KMLFileTokenParse(pstr,NULL,0,
							 ktag,
							 fid));
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
char* KMLFileTokenParse(char* pstr,int *m,int maxlen,
						char* ktag,
						FILE* fid){

	char*   kstr;

/*  get next token and allocate if necessary  */

	if (!(kstr=KMLFileToken(fid,
							NULL,NULL)) ||
		(kstr[0] == '<'))
		_error2_("KMLFileTokenParse -- Missing string field for " << ktag << ".\n");

	if (!pstr) {
		if (maxlen)
			pstr=(char *) xmalloc((maxlen      +1)*sizeof(char));
		else
			pstr=(char *) xmalloc((strlen(kstr)+1)*sizeof(char));
	}

	if (maxlen && (maxlen < strlen(kstr))) {
		_pprintLine_("KMLFileTokenParse -- string field too short for " << ktag << ".");
		_pprintLine_("KMLFileTokenParse -- \"" << kstr << "\" truncated to " << maxlen << " characters.");
		strncpy(pstr,kstr,maxlen);
	}
	else
		memcpy(pstr,kstr,(strlen(kstr)+1)*sizeof(char));

	xfree((void**)&kstr);

	if (m)
		*m=strlen(pstr);

/*  get additional token and compare to closing tag  */

	if (ktag)
		if (!(kstr=KMLFileToken(fid,
								NULL,NULL)) ||
			(kstr[0] != '<') ||
			(kstr[1] != '/') ||
			(strncmp(&(kstr[2]),&(ktag[1]),strlen(ktag)-1)))
		  {_error2_("KMLFileTokenParse -- Missing closing tag for " << ktag << ".\n");}
		else
			xfree((void**)&kstr);

//	_pprintLine_("KMLFileTokenParse -- " << ktag << "=\"" << pstr << "\".");

	return(pstr);
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
int KMLFileTokenParse(float* pfval,
					  char* ktag,
					  FILE* fid){

	char*   kstr;

/*  get next token and convert to appropriate format  */

	if (!(kstr=KMLFileToken(fid,
							NULL,NULL)) ||
		(kstr[0] == '<'))
	  {_error2_("KMLFileTokenParse -- Missing integer field for " << ktag << ".\n");}

	sscanf(kstr,"%g",pfval);
	xfree((void**)&kstr);

/*  get additional token and compare to closing tag  */

	if (ktag)
		if (!(kstr=KMLFileToken(fid,
								NULL,NULL)) ||
			(kstr[0] != '<') ||
			(kstr[1] != '/') ||
			(strncmp(&(kstr[2]),&(ktag[1]),strlen(ktag)-1)))
		  {_error2_("KMLFileTokenParse -- Missing closing tag for " << ktag << ".\n");}
		else
			xfree((void**)&kstr);

//	_pprintLine_("KMLFileTokenParse -- " << ktag << "=" << *pfval << ".");

	return(0);
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
int KMLFileTokenParse(double* pdval,
					  char* ktag,
					  FILE* fid){

	char*   kstr;

/*  get next token and convert to appropriate format  */

	if (!(kstr=KMLFileToken(fid,
							NULL,NULL)) ||
		(kstr[0] == '<'))
		_error2_("KMLFileTokenParse -- Missing integer field for " << ktag << ".\n");

	sscanf(kstr,"%lg",pdval);
	xfree((void**)&kstr);

/*  get additional token and compare to closing tag  */

	if (ktag)
		if (!(kstr=KMLFileToken(fid,
								NULL,NULL)) ||
			(kstr[0] != '<') ||
			(kstr[1] != '/') ||
			(strncmp(&(kstr[2]),&(ktag[1]),strlen(ktag)-1)))
		  {_error2_("KMLFileTokenParse -- Missing closing tag for " << ktag << ".\n");}
		else
			xfree((void**)&kstr);

//	_pprintLine_("KMLFileTokenParse -- " << ktag << "=" << *pdval << ".");

	return(0);
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
int KMLFileTokenParse(double **pdval,int* m,int maxlen,
					  char* ktag,
					  FILE* fid){

	int     i=-1,j;
	char*   kstr;
	char*   ktok;
	char    delim[]={' ',',','\f','\n','\r','\t','\v','\0'};

/*  get next token and allocate if necessary  */

	if (!(kstr=KMLFileToken(fid,
							NULL,NULL)) ||
		(kstr[0] == '<'))
		_error2_("KMLFileTokenParse -- Missing double [m] field for " << ktag << ".\n");

	if (!*pdval)
		if (maxlen)
			*pdval=(double *) xmalloc(maxlen              *sizeof(double));
		else
			*pdval=(double *) xmalloc(((strlen(kstr)+1)/2)*sizeof(double));

/*  loop through string to get all values  */

	ktok=strtok(kstr,delim);
	while (ktok) {
		i++;
		if (maxlen && (maxlen < i+1))
			_error2_("KMLFileTokenParse -- Double [m] field too short for " << ktag << ".\n");
		sscanf(ktok,"%lg",&((*pdval)[i]));
		ktok=strtok(NULL,delim);
	}
	xfree((void**)&kstr);

	if (!maxlen)
		*pdval=(double *) xrealloc(*pdval,(i+1)*sizeof(double));

	if (m)
		*m=i+1;

/*  get additional token and compare to closing tag  */

	if (ktag)
		if (!(kstr=KMLFileToken(fid,
								NULL,NULL)) ||
			(kstr[0] != '<') ||
			(kstr[1] != '/') ||
			(strncmp(&(kstr[2]),&(ktag[1]),strlen(ktag)-1)))
		  {_error2_("KMLFileTokenParse -- Missing closing tag for " << ktag << ".\n");}
		else
			xfree((void**)&kstr);

//	_pprintLine_("KMLFileTokenParse -- " << ktag << "=...");
//	for (j=0; j<=i; j++)
//		_pprintLine_("   [" << j << "]: " << (*pdval)[j] << "g");

	return(0);
}
/*}}}*/
/*FUNCTION  KMLFileTokenParse {{{*/
int KMLFileTokenParse(double (**pdval3)[3],int* m,int maxlen,
					  char* ktag,
					  FILE* fid){

	int     i=0,j=-1;
	char*   kstr;
	char*   ktok;
	char    delim[]={' ',',','\f','\n','\r','\t','\v','\0'};

/*  get next token and allocate if necessary  */

	if (!(kstr=KMLFileToken(fid,
							NULL,NULL)) ||
		(kstr[0] == '<'))
		_error2_("KMLFileTokenParse -- Missing double [m x 3] field for " << ktag << ".\n");

	if (!*pdval3)
		if (maxlen)
			*pdval3=(double (*)[3]) xmalloc((maxlen*3)          *sizeof(double));
		else
			*pdval3=(double (*)[3]) xmalloc(((strlen(kstr)+1)/2)*sizeof(double));

/*  loop through string to get all values  */

	ktok=strtok(kstr,delim);
	while (ktok) {
		j++;
		if (j == 3) {
			i++;
			j=0;
			if (maxlen && (maxlen < i+1))
				_error2_("KMLFileTokenParse -- Double [m x 3] field too short for " << ktag << ".\n");
		}
		sscanf(ktok,"%lg",&((*pdval3)[i][j]));
		ktok=strtok(NULL,delim);
	}
	xfree((void**)&kstr);

	if (!maxlen)
		*pdval3=(double (*)[3]) xrealloc(*pdval3,((i+1)*3)*sizeof(double));

	if (m)
		*m=i+1;

	if (j != 2)
		_pprintLine_("KMLFileTokenParse -- Double [m x 3] field for " << ktag << " does not have multiple of 3 values.");

/*  get additional token and compare to closing tag  */

	if (ktag)
		if (!(kstr=KMLFileToken(fid,
								NULL,NULL)) ||
			(kstr[0] != '<') ||
			(kstr[1] != '/') ||
			(strncmp(&(kstr[2]),&(ktag[1]),strlen(ktag)-1)))
		  {_error2_("KMLFileTokenParse -- Missing closing tag for " << ktag << ".\n");}
		else
			xfree((void**)&kstr);

//	_pprintLine_("KMLFileTokenParse -- " << ktag << "=...");
//	for (j=0; j<=i; j++)
//		_pprintLine_("   [" << j << "][0-2]: " << (*pdval3)[j][0] << "g," << (*pdval3)[j][1] << "g," << (*pdval3)[j][2] << "g");

	return(0);
}
/*}}}*/
/*FUNCTION  KMLFileTagSkip {{{*/
int KMLFileTagSkip(char* ktag,
				   FILE* fid){

	char*   kstr;

/*  note that tags of the same type can be nested inside each other, so for each
	opening tag, must find corresponding closing tag  */

	_pprintLine_("KMLFileTagSkip -- input tag " << ktag << ".");

/*  if next token is a closing tag, compare to input  */

	while (kstr=KMLFileToken(fid,
							 NULL,NULL)) {
		if      ((kstr[0] == '<') &&
				 (kstr[1] == '/') &&
				 (!strncmp(&(kstr[2]),&(ktag[1]),(strcspn(ktag," >")-1)/sizeof(char)))) {
			_pprintLine_("KMLFileTagSkip -- closing tag " << kstr << ".");
			xfree((void**)&kstr);
			return(0);
		}

/*  if next token is an opening tag, call recursively  */

		else if ((kstr[0] == '<') &&
				 (kstr[1] != '/')) {
			_pprintLine_("KMLFileTagSkip -- opening tag " << kstr << ".");
			KMLFileTagSkip(kstr,
						   fid);
		}

/*  if next token is a closing tag, error out  */

		else if ((kstr[0] == '<') &&
				 (kstr[1] == '/')) {
			_error2_("KMLFileTagSkip -- Unexpected closing tag " << kstr << ".\n");
		}

		xfree((void**)&kstr);
	}

	_error2_("KMLFileTokenParse -- Corresponding closing tag for " << ktag << " not found.\n");

	return(0);
}
/*}}}*/
