#include "krcd.h"
#include <sys/stat.h>

/* ************************************************************************** */
/* ********************************* Input ********************************** */
/* ************************************************************************** */

static int krcdParseBoolean(xmlNodePtr base, char *attrname, char *filename) {
    xmlChar *temp = xmlGetProp(base,(xmlChar *) attrname);
    static char *yup[] = { "1", "true", "yes", "on", NULL };
    static char *nope[] = { "0", "false", "no", "off", NULL };
    int i;

    for ( i=0; yup[i]!=NULL; ++i )
	if ( xmlStrcasecmp(temp,(xmlChar *) yup[i])==0 ) {
	    xmlFree(temp);
return( 1 );
	}

    for ( i=0; nope[i]!=NULL; ++i )
	if ( xmlStrcasecmp(temp,(xmlChar *) nope[i])==0 ) {
	    xmlFree(temp);
return( 0 );
	}

    fprintf( stderr, "%s:%ld: '%s' is an unknown boolean value used to set '%s'\n",
	    filename, xmlGetLineNo(base), (char *) temp, attrname );
    xmlFree(temp);
return( 0 );
}

static int krcdParseInteger(xmlNodePtr base, char *attrname, char *filename) {
    xmlChar *temp = xmlGetProp(base,(xmlChar *) attrname);
    char *end;
    int val;

    val = strtol((char *) temp, &end, 10 );
    if ( *end!='\0' ) {
	fprintf( stderr, "%s:%ld: Bad integer value, %s used to set '%s'\n",
		filename, xmlGetLineNo(base), (char *) temp, attrname );
    }
return( val );
}

static KRCD_BOX *krcdParseBox(xmlNodePtr boxNode, char *filename, KRCD_BOX *parent) {
    KRCD_BOX *box;
    xmlAttrPtr attrs;

    box = calloc(1,sizeof(KRCD_BOX));
    box->bars = parent==NULL ? -1 : parent->bars;	/* If the child of a meanwhile, the number of bars should be set on the meanwhile, and all kids have the same bars */
    box->drawDivider = parent==NULL;
    for ( attrs = boxNode->properties; attrs!=NULL; attrs = attrs->next ) {
	if ( attrs->type==XML_ATTRIBUTE_NODE ) {
	    if ( xmlStrcmp(attrs->name,(xmlChar *) "header" )==0 )
		box->header = (char *) xmlGetProp(boxNode,(xmlChar *) "header");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "label" )==0 )
		box->label = (char *) xmlGetProp(boxNode,(xmlChar *) "label");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "notes-top" )==0 )
		box->topnotes = (char *) xmlGetProp(boxNode,(xmlChar *) "notes-top");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "notes-bottom" )==0 )
		box->bottomnotes = (char *) xmlGetProp(boxNode,(xmlChar *) "notes-bottom");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "notes-bottom2" )==0 )
		box->bottomnotes2 = (char *) xmlGetProp(boxNode,(xmlChar *) "notes-bottom2");

	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "line-break-after" )==0 )
		box->line_break_after = krcdParseBoolean(boxNode,"line-break-after",filename);

	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "bars" )==0 ) {
		int val = krcdParseInteger(boxNode,"bars",filename);
		if ( box->bars!=-1 )
		    fprintf( stderr, "%s:%ld: Attempt to set the bars attribute on the child of a meanwhile%s\n",
			    filename, xmlGetLineNo(boxNode), val==box->bars ? " but at least the two values match." : " and the two values are different." );
		box->bars = val;
	    }

	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "text" )==0 )
		box->text[0] = (char *) xmlGetProp(boxNode,(xmlChar *) "text");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "modifier" )==0 )
		box->modifier[0] = (char *) xmlGetProp(boxNode,(xmlChar *) "modifier");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "half" )==0 )
		box->half[0] = krcdParseBoolean(boxNode,"half",filename);

	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "text2" )==0 )
		box->text[1] = (char *) xmlGetProp(boxNode,(xmlChar *) "text2");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "modifier2" )==0 )
		box->modifier[1] = (char *) xmlGetProp(boxNode,(xmlChar *) "modifier2");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "half2" )==0 )
		box->half[1] = krcdParseBoolean(boxNode,"half2",filename);

	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "text3" )==0 )
		box->text[2] = (char *) xmlGetProp(boxNode,(xmlChar *) "text3");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "modifier3" )==0 )
		box->modifier[2] = (char *) xmlGetProp(boxNode,(xmlChar *) "modifier3");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "half3" )==0 )
		box->half[2] = krcdParseBoolean(boxNode,"half3",filename);

	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "text4" )==0 )
		box->text[3] = (char *) xmlGetProp(boxNode,(xmlChar *) "text4");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "modifier4" )==0 )
		box->modifier[3] = (char *) xmlGetProp(boxNode,(xmlChar *) "modifier4");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "half4" )==0 )
		box->half[3] = krcdParseBoolean(boxNode,"half4",filename);

	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "divider" )==0 )
		box->drawDivider = krcdParseBoolean(boxNode,"divider",filename);
	    else
		fprintf( stderr, "%s:%ld: Unexpected attribute: %s in box node\n", filename, xmlGetLineNo(boxNode), (char *) attrs->name );
	}
    }
    if ( box->bars==-1 )
	fprintf( stderr, "%s:%ld: 'bars' attribute must be specified in a box node\n", filename, xmlGetLineNo(boxNode) );

    /* If a box has any children they should be svg nodes, which we copy without */
    /*  looking at */
    if ( boxNode->children!=NULL )
	box->svg_description = xmlCopyNodeList(boxNode->children);

    if ( box->label!=NULL ) {
	if ( box->svg_description!=NULL )
	    krcdLibraryAddFigure(box->label,box->svg_description);
	else {
	    box->svg_description = krcdLibraryFindFigure(box->label);
	    if ( box->svg_description==NULL )
		fprintf( stderr, "%s:%ld: Look up of figure: %s failed, no svg description found\n", filename, xmlGetLineNo(boxNode), box->label );
	}
    }
return( box );
}

static KRCD_BOX *krcdParseMeanwhile(xmlNodePtr boxNode, char *filename, int ismeanwhile ) {
    KRCD_BOX *box;
    xmlNodePtr child;
    xmlAttrPtr attrs;
    KRCD_BOX *newBox, *lastBox;
    int kidbars;

    box = calloc(1,sizeof(KRCD_BOX));
    box->meanwhile = ismeanwhile==1;
    box->sequence  = ismeanwhile==-1;
    box->drawDivider = true;
    box->bars = -1;
    for ( attrs = boxNode->properties; attrs!=NULL; attrs = attrs->next ) {
	if ( attrs->type==XML_ATTRIBUTE_NODE ) {
	    if ( xmlStrcmp(attrs->name,(xmlChar *) "bars" )==0 )
		box->bars = krcdParseInteger(boxNode,"bars",filename);
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "line-break-after" )==0 )
		box->line_break_after = krcdParseBoolean(boxNode,"line-break-after",filename);
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "header" )==0 )
		box->header = (char *) xmlGetProp(boxNode,(xmlChar *) "header");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "notes-top" )==0 )
		box->topnotes = (char *) xmlGetProp(boxNode,(xmlChar *) "notes-top");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "notes-bottom" )==0 )
		box->bottomnotes = (char *) xmlGetProp(boxNode,(xmlChar *) "notes-bottom");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "notes-bottom2" )==0 )
		box->bottomnotes2 = (char *) xmlGetProp(boxNode,(xmlChar *) "notes-bottom2");
	    else
		fprintf( stderr, "%s:%ld: Unexpected attribute: %s in meanwhile node\n", filename, xmlGetLineNo(boxNode), (char *) attrs->name );
	}
    }
    if ( box->bars==-1 && ismeanwhile==1 )
	fprintf( stderr, "%s:%ld: 'bars' attribute must be specified in a meanwhile node\n", filename, xmlGetLineNo(boxNode) );
    else if ( box->bars!=-1 && ismeanwhile!=1 )
	fprintf( stderr, "%s:%ld: 'bars' attribute must not be specified in a sequence node\n", filename, xmlGetLineNo(boxNode) );

    lastBox = NULL;
    kidbars = 0;
    for ( child = boxNode->children; child!=NULL; child = child->next ) {
	if ( child->type==XML_ELEMENT_NODE ) {
	    if (xmlStrcmp(child->name,(xmlChar *) "box")==0 )
		newBox = krcdParseBox(child,filename,box);
	    else if (xmlStrcmp(child->name,(xmlChar *) "meanwhile")==0 ||
		    xmlStrcmp(child->name,(xmlChar *) "list")==0 ||
		    xmlStrcmp(child->name,(xmlChar *) "sequence")==0 ) {
		fprintf( stderr, "%s:%ld: nested meanwhiles or sequences are not supported\n", filename, xmlGetLineNo(boxNode) );
    continue;
	    } else {
		fprintf( stderr, "Unexpected child: %s in krcd node in %s\n", (char *) attrs->name, filename );
    continue;
	    }
	    if ( lastBox == NULL )
		box->boxes = newBox;
	    else
		lastBox->next = newBox;
	    kidbars += newBox->bars;
	    lastBox = newBox;
	}
    }
    if ( ismeanwhile!=1 )
	box->bars = kidbars;

return( box );
}

KRCD *krcdParseKrcd(xmlDocPtr doc, xmlNodePtr krcdNode, char *filename) {
    KRCD *krcd;
    xmlNodePtr child;
    xmlAttrPtr attrs;
    KRCD_BOX *newBox, *lastBox;
    struct stat statb;

    if ( xmlStrcmp(krcdNode->name,(xmlChar *) "krcd")!=0 ) {
	fprintf(stderr,"Expected <krcd> node, but got <%s> in %s", krcdNode->name, filename );
return( NULL );
    }

    krcd = calloc(1,sizeof(KRCD));
    for ( attrs = krcdNode->properties; attrs!=NULL; attrs = attrs->next ) {
	if ( attrs->type==XML_ATTRIBUTE_NODE ) {
	    if ( xmlStrcmp(attrs->name,(xmlChar *) "header-left" )==0 )
		krcd->header_left = (char *) xmlGetProp(krcdNode,(xmlChar *) "header-left");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "header-right" )==0 )
		krcd->header_right = (char *) xmlGetProp(krcdNode,(xmlChar *) "header-right");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "footer-left" )==0 )
		krcd->footer_left = (char *) xmlGetProp(krcdNode,(xmlChar *) "footer-left");
	    else if ( xmlStrcmp(attrs->name,(xmlChar *) "footer-right" )==0 )
		krcd->footer_right = (char *) xmlGetProp(krcdNode,(xmlChar *) "footer-right");
	    else
		fprintf( stderr, "%s:%ld: Unexpected attribute: %s in krcd node\n", filename, xmlGetLineNo(krcdNode), (char *) attrs->name );
	}
    }
    if ( krcd->header_left==NULL )
	fprintf( stderr, "Missing 'header_left' in krcd in %s\n", filename );
    if ( krcd->header_right==NULL )
	fprintf( stderr, "Missing 'header_right' in krcd in %s\n", filename );
    if ( krcd->footer_left==NULL )
	fprintf( stderr, "Missing 'footer_left' in krcd in %s\n", filename );
    if ( krcd->footer_right==NULL )
	fprintf( stderr, "Missing 'footer_lright' in krcd in %s\n", filename );

    lastBox = NULL;
    for ( child = krcdNode->children; child!=NULL; child = child->next ) {
	if ( child->type==XML_ELEMENT_NODE ) {
	    if (xmlStrcmp(child->name,(xmlChar *) "box")==0 )
		newBox = krcdParseBox(child,filename,NULL);
	    else if (xmlStrcmp(child->name,(xmlChar *) "meanwhile")==0 )
		newBox = krcdParseMeanwhile(child,filename, true);
	    else if (xmlStrcmp(child->name,(xmlChar *) "sequence")==0 )
		newBox = krcdParseMeanwhile(child,filename, -1);
	    else if (xmlStrcmp(child->name,(xmlChar *) "list")==0 )
		newBox = krcdParseMeanwhile(child,filename, 0);
	    else {
		fprintf( stderr, "Unexpected child: %s in krcd node in %s\n", (char *) attrs->name, filename );
    continue;
	    }
	    if ( lastBox == NULL )
		krcd->boxes = newBox;
	    else
		lastBox->next = newBox;
	    lastBox = newBox;
	}
    }

    krcd->doc = xmlNewDoc( (xmlChar *) "1.0" );

    if ( stat(filename,&statb)!=-1 ) {
	krcd->last_mod_time = statb.st_mtime;
#ifdef __APPLE__
	krcd->creation_time = statb.st_birthtime;
#endif
    }

return( krcd );
}

KRCD *krcdLoadFile(char *filename) {
    xmlDocPtr doc;
    xmlNodePtr top;
    KRCD *krcd;

    doc = xmlParseFile(filename);
    if ( doc==NULL ) {
	fprintf( stderr, "Failed to open: %s\n", filename );
return( NULL );
    }
    top = xmlDocGetRootElement(doc);
    if ( xmlStrcmp(top->name,(xmlChar *) "krcd")!=0 ) {
	fprintf(stderr,"Wrong type of xml file (%s) in %s", top->name, filename );
	xmlFreeDoc(doc);
return( NULL );
    }

    krcd = krcdParseKrcd(doc,top, filename);
    xmlFreeDoc(doc);
return( krcd );
}

/* ************************************************************************** */
/* ********************************* Output ********************************* */
/* ************************************************************************** */

static int allWhiteSpace(xmlChar *temp) {

    if ( temp==NULL )
return( 1 );
    while ( *temp )
	if ( !isspace(*temp++) )
return( 0 );

return( 1 );
}

void krcdOutputIndent(FILE *output,int indent) {
    int i;

    for ( i=0; i<indent; ++i )
	putc(' ',output);
}

static void entityOutput(FILE *out, char *str, int donl ) {
    while ( *str ) {
	if ( *str=='&' )
	    fputs("&amp;",out);
	else if ( *str=='"' )
	    fputs("&quot;",out);
	else if ( *str=='<' )
	    fputs("&lt;",out);
	else if ( *str=='>' )
	    fputs("&gt;",out);
	else if ( *str=='\n' && donl==1 )
	    fputs("&#10;",out);
	else if ( *str=='\n' && donl==2 )
	    fputs("<br/>",out);
	else
	    fputc(*str,out);
	++str;
    }
}

static void krcdOutputProp(FILE *output,int indent, char *name, char *value ) {
    if ( value==NULL )
return;
    putc('\n', output );
    krcdOutputIndent(output,indent+4);
    fprintf( output, "%s=\"", name );
    entityOutput(output,value,false);
    putc('"', output );
}

void krcdOutputNode(FILE *output,int indent, xmlDocPtr doc, xmlNodePtr node, int doMassage) {
    xmlNodePtr child;
    xmlAttrPtr attrs;
    xmlChar *temp;
    
    if ( doMassage && node->type==XML_ELEMENT_NODE &&
	    xmlStrcmp(node->name,(xmlChar *) "text")==0 &&
	    krcdTextNeedsMassaging(doc,node)) {
	xmlNodePtr massaged, n;
	massaged = krcdMassageText(doc,node);
	for ( n=massaged ; n!=NULL; n=n->next )
	    krcdOutputNode(output,indent, doc, n, false);
	xmlFreeNodeList(massaged);
return;
    }
    krcdOutputIndent(output,indent);
    fprintf( output, "<%s ", (char *) (node->name) );
    for ( attrs = node->properties; attrs!=NULL; attrs = attrs->next ) {
	if ( attrs->type==XML_ATTRIBUTE_NODE ) {
	    /* I presume there's a better way to get the value of an attribute */
	    /*  than this, but I can'f figure it out */
	    temp = xmlGetProp(node,attrs->name);
	    krcdOutputProp(output,indent,(char *) attrs->name, (char *) temp);
	    xmlFree(temp);
	}
    }
    if ( xmlFirstElementChild(node)!=NULL ) {
	fprintf( output, ">\n" );
	for ( child=node->children; child!=NULL; child=child->next ) {
	    if ( child->type == XML_ELEMENT_NODE )
		krcdOutputNode(output,indent+2,doc,child, doMassage);
	}
	krcdOutputIndent(output,indent);
	fprintf( output, "</%s>\n", (char *) (node->name) );
    } else if ( node->children==NULL ) {
	fprintf( output, "/>\n" );
    } else {
	temp = xmlNodeListGetString(doc,node->children, 1);
	if ( temp==NULL || allWhiteSpace(temp))
	    fprintf( output, "/>\n" );
	else {
	    putc('>',output);
	    entityOutput(output,(char *) temp,false);
	    fprintf( output, "</%s>\n", (char *) (node->name));
	}
    }
}
	    
static void krcdOutputBox(FILE *output, int indent, xmlDocPtr doc, KRCD_BOX *box, int output_bars) {
    char buffer[12];
    xmlNodePtr kids;

    krcdOutputIndent(output,indent);
    fprintf( output, "<box " );
    if ( output_bars ) {
	sprintf( buffer, "%d", box->bars );
	krcdOutputProp(output,indent,"bars", buffer);
    }
    if ( box->line_break_after )
	krcdOutputProp(output,indent,"line-break-after", "true" );
    krcdOutputProp(output,indent,"header", box->header );
    krcdOutputProp(output,indent,"notes-top", box->topnotes );
    krcdOutputProp(output,indent,"notes-bottom", box->bottomnotes );
    krcdOutputProp(output,indent,"notes-bottom2", box->bottomnotes2 );
    krcdOutputProp(output,indent,"label", box->label );
    krcdOutputProp(output,indent,"text", box->text[0] );
    krcdOutputProp(output,indent,"modifier", box->modifier[0] );
    if ( box->half[0] )
	krcdOutputProp(output,indent,"half", "true" );
    krcdOutputProp(output,indent,"text2", box->text[1] );
    krcdOutputProp(output,indent,"modifier2", box->modifier[1] );
    if ( box->half[1] )
	krcdOutputProp(output,indent,"half2", "true" );
    krcdOutputProp(output,indent,"text3", box->text[2] );
    krcdOutputProp(output,indent,"modifier3", box->modifier[3] );
    if ( box->half[2] )
	krcdOutputProp(output,indent,"half3", "true" );
    krcdOutputProp(output,indent,"text4", box->text[2] );
    krcdOutputProp(output,indent,"modifier4", box->modifier[3] );
    if ( box->half[3] )
	krcdOutputProp(output,indent,"half4", "true" );

    if ( !box->drawDivider )
	krcdOutputProp(output,indent,"divider", "no" );
    fprintf( output, ">\n" );

    for ( kids = box->svg_description; kids!=NULL; kids = kids->next ) {
	if ( kids->type == XML_ELEMENT_NODE )
	    krcdOutputNode(output,indent+2,doc,kids,false);
    }

    krcdOutputIndent(output,indent);
    fprintf( output, "</box>\n" );
}

static void krcdOutputMeanwhile(FILE *output, int indent, xmlDocPtr doc, KRCD_BOX *meanwhile) {
    KRCD_BOX *boxes;
    char buffer[12];

    krcdOutputIndent(output,indent);
    fprintf( output, meanwhile->meanwhile ? "<meanwhile " :
	    meanwhile->sequence ? "<sequence " :
	    "<list ");
    if ( meanwhile->meanwhile ) {
	sprintf( buffer, "%d", meanwhile->bars );
	krcdOutputProp(output,indent,"bars", buffer);
    }
    fprintf( output, ">\n" );

    for ( boxes = meanwhile->boxes; boxes!=NULL; boxes = boxes->next ) {
	krcdOutputBox(output,indent+2,doc,boxes,false);
    }

    krcdOutputIndent(output,indent);
    fprintf( output, meanwhile->meanwhile ? "</meanwhile>\n" :
	    meanwhile->sequence ? "</sequence>\n" :
	    "</list\n>" );
}

void krcdOutputKrcd(FILE *output, int indent, KRCD *krcd) {
    KRCD_BOX *boxes;

    krcdOutputIndent(output,indent);
    fprintf( output, "<krcd " );
    krcdOutputProp(output,indent,"header-left", krcd->header_left);
    krcdOutputProp(output,indent,"header-right", krcd->header_right);
    krcdOutputProp(output,indent,"footer-left", krcd->footer_left);
    krcdOutputProp(output,indent,"footer-right", krcd->footer_right);
    fprintf( output, ">\n" );

    for ( boxes = krcd->boxes; boxes!=NULL; boxes = boxes->next ) {
	if ( boxes->boxes!=NULL )
	    krcdOutputMeanwhile(output,indent+2,krcd->doc,boxes);
	else
	    krcdOutputBox(output,indent+2,krcd->doc,boxes,true);
    }

    krcdOutputIndent(output,indent);
    fprintf( output, "</krcd>\n" );
}

void krcdOutputFile(char *filename, KRCD *krcd) {
    FILE *output;

    output = fopen(filename,"w" );
    if ( output==NULL ) {
	fprintf( stderr, "Failed to open output file: %s\n", filename );
return;
    }

    fprintf( output, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
    krcdOutputKrcd(output,0,krcd);
    fclose( output );

    krcdLibrarySaveOnExit();
}

/* ************************************************************************** */
/* ********************************** Free ********************************** */
/* ************************************************************************** */
static void krcdFreeBox(KRCD_BOX *box) {
    KRCD_BOX *boxes, *next;
    int i;

    for ( boxes = box->boxes; boxes!=NULL; ) {
	next = boxes->next;
	krcdFreeBox(boxes);
	boxes = next;
    }
    xmlFreeNodeList(box->svg_description);

    for ( i=0; i<4; ++i ) {
	free( box->text[i]);
	free( box->modifier[i]);
    }
    free( box->header );
    free( box->topnotes );
    free( box->bottomnotes );
    free( box->bottomnotes2 );
    free( box->label );

    free( box );
}
	
void krcdFree(KRCD *krcd) {
    KRCD_BOX *boxes, *next;

    free( krcd->header_left );
    free( krcd->header_right );
    free( krcd->footer_left );
    free( krcd->footer_right );

    for ( boxes = krcd->boxes; boxes!=NULL; ) {
	next = boxes->next;
	krcdFreeBox(boxes);
	boxes = next;
    }

    xmlFreeDoc(krcd->doc);
    free(krcd);
}
