#include "squaredance.h"

static char *honourNames[] = { "HONOUR_Default", "HONOUR_DoIt", "HONOUR_Dont", NULL };
static int local_variable_number=1;

static void danceJSOutputDanceContents(FILE *out, Dance *dance, int indent);
static void danceJSOutputBlock(FILE *out,Block *b,int indent);


static void doIndent(FILE *out,int indent) {
    int i;

    for ( i=0; i+8<=indent; i+=8 )
	putc('\t',out);
    for ( ; i<indent; ++i )
	putc(' ',out);
}

static void outputIntProperty(FILE *out,char *tag,int val,int indent) {
    doIndent(out,indent);
    fprintf( out, "%s: %d,\n", tag, val );
}

static void outputHexProperty(FILE *out,char *tag,int val,int indent) {
    doIndent(out,indent);
    fprintf( out, "%s: 0x%x,\n", tag, val );
}

static void outputBooleanProperty(FILE *out,char *tag,int val,int indent) {
    doIndent(out,indent);
    fprintf( out, "%s: %s,\n", tag, val ? "true" : "false" );
}

static void outputFloatProperty(FILE *out,char *tag,double val,int indent) {
    char buffer[50], *pt;

    sprintf( buffer, "%.4f", val );
    for ( pt=buffer+strlen(buffer); pt>buffer && pt[-1]=='0'; --pt );
    *pt='\0';
    doIndent(out,indent);
    fprintf( out, "%s: %s,\n", tag, buffer );
}

static void outputString(FILE *out, char *str) {
    unsigned char *pt;
    int ch;

    if ( str==NULL )
	fprintf( out, "null" );
    else {
	putc('\"', out);
	for ( pt=(unsigned char *) str; (ch = *pt); ++pt ) {
#if 0
	    if ( (ch>=0xc0 && ch<=0xdf) && (pt[0]&0x80) ) {
		ch &= 0x1f;
		ch <<= 6;
		ch |= ( *pt++ & 0x3f);
	    } else if ( (ch>0xe0 && ch<=0xef) && (pt[0]&0x80) && (pt[1]&0x80)) {
		ch &= 0xf;
		ch <<= 12;
		ch |= (( pt[0] & 0x3f)<<6) | ( pt[1] & 0x3f);
		pt += 2;
	    } else if ( (ch>0xf0 && ch<=0xf7) && (pt[0]&0x80) && (pt[1]&0x80)) && (pt[2]&0x80)) {
		ch &= 0x7;
		ch <<= 18;
		ch |= (( pt[0] & 0x3f)<<12) | (( pt[1] & 0x3f)<<6) | (pt[2] & 0x3f);
		pt += 3;
	    } else if ( (ch>0xf0 && ch<=0xf7) && (pt[0]&0x80) && (pt[1]&0x80)) && (pt[2]&0x80) && (pt[3]&0x80)) {
		/* Yeah, I know, this was disallowed */
		ch &= 0x7;
		ch <<= 18;
		ch |= (( pt[0] & 0x3f)<<18) | (( pt[1] & 0x3f)<<12) | ((pt[2] & 0x3f)<<6) | (pt[3] & 0x3f);
		pt += 4;
	    }
#endif
	    
	    if ( ch=='\n' )
		fputs("\\n",out);
	    else if ( ch=='\t' )
		fputs("\\t",out);
	    else {
		if ( ch=='"' || ch=='\\' )
		    putc('\\',out);
		putc(ch,out);
	    }
	}
	putc('\"', out);
    }
}

static void outputStringProperty(FILE *out,char *tag,char *val,int indent) {

    doIndent(out,indent);
    fprintf( out, "%s: ", tag );
    outputString( out, val );
    fprintf( out, ",\n" );
}

static void outputEnumProperty(FILE *out,char *tag,int val,char **names,int indent) {
    doIndent(out,indent);
    fprintf( out, "%s: %s,\n", tag, names[val] );
}

static void figureAnimationYear(FILE *out,Dance *dance,int indent) {
    int year;
    char *end;

    if ( dance->animation_date == NULL )
return;
    year = strtol( dance->animation_date,&end,10 );
    if ( year<2017 )
return;
    outputIntProperty(out,"yearAnimated",year,indent);
}

static void danceJSUntickComponents(Dance *dance) {
    int i;
    for ( i=0; i<dance->comp_cnt; ++i ) {
	danceJSUntickComponents(dance->components[i]);
	dance->components[i]->choreo_pos = 0;
    }
}

static void danceJSOutputComponents(FILE *out, Dance *dance, int indent) {
    /* Components are potentially refered to multiple times in the choreography list */
    int i;

    for ( i=0; i<dance->comp_cnt; ++i ) {
	Dance *sub = dance->components[i];
	if ( sub->choreo_pos!=0 )
    continue;
	sub->choreo_pos = ++local_variable_number;
	danceJSOutputComponents(out,sub,indent);
	doIndent(out,indent);
	fprintf( out, "let v%d = ", sub->choreo_pos );
	danceJSOutputDanceContents(out,sub,indent+2);
	fprintf( out, ";\n" );
    }
}

static void danceJSOutputHeader(FILE *out, Dance *dance) {

    local_variable_number = 0;

    fprintf( out, "{\n" );	/* Define a new scope for temporary variables needed for choreographies */
    danceJSUntickComponents(dance);
    danceJSOutputComponents(out,dance,2);
    
    fprintf( out, "  registerDance( " );
}

static void danceJSOutputChoreography(FILE *out,Choreography *choreo,int indent) {
    char buffer[50], *pt;

    sprintf( buffer, "%.4f", choreo->time_scale );
    for ( pt=buffer+strlen(buffer); pt>buffer && pt[-1]=='0'; --pt );
    *pt='\0';

    doIndent(out,indent);
    fprintf( out, "{ figure: v%d, active: %d, timeScale: %s }",
	    choreo->figure->choreo_pos, choreo->active, buffer );
}

static void danceJSOutputCallList(FILE *out,CallList *call,int indent) {

    doIndent(out,indent);
    fprintf( out, "{ callName: " );
    outputString(out, call->callName );
    fprintf( out, ", millibars: %d, partMask: 0x%x }", call->millibars, call->part_mask );
    /* No support for audio !!!! */
}

static void danceJSOutputCondition(FILE *out,Condition *c,int indent) {
    if ( c==NULL )
	fprintf( out, "null" );
    else if ( c->operator == op_and || c->operator==op_or ) {
	fprintf( out, "{ operator: %s, op1: ", c->operator==op_and ? "OP_AND" : "OP_OR" );
	danceJSOutputCondition(out,c->op1,indent);
	fputs( ", op2: ", out);
	danceJSOutputCondition(out,c->op1,indent);
    } else if ( c->operator == op_not ) {
	fprintf( out, "{ operator: OP_NOT, op1: " );
	danceJSOutputCondition(out,c->op1,indent);
    } else {
	fprintf( out, "{ operator: %s, argument1: %d ",
		c->operator==op_topset ? "OP_TOPSET" :
		c->operator==op_bottomset ? "OP_BOTTOMSET" :
		c->operator==op_majorpos ? "OP_MAJORPOS" :
		c->operator==op_part ? "OP_PART" :
		c->operator==op_last ? "OP_LAST" :
		    "OP_FIRST",
		c->argument1 );
	if ( c->operator==op_topset || c->operator==op_bottomset )
	    fprintf( out, ", argument2: %d ", c->argument2 );
    }
    putc('}', out);
}

static void danceJSOutputFigureSegment(FILE *out,FigureSegment *cs,int indent) {
    int i;

    doIndent(out,indent);
    fprintf( out, "{ cmd: %s, millibars: %d, orient: %s, degrees: %d, faceDancer: %d,\n",
	    commandNames[cs->cmd], cs->millibars, orientationNames[cs->orient],
	    (int) round(cs->degrees), cs->faceDancer );
    if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
	doIndent(out,indent+4);
	fprintf( out, "endX: %.1f, endY: %.1f,", cs->endPoint.x, cs->endPoint.y );
	if ( cs->cmd==c_curveto )
	    fprintf( out, " cp1X: %.1f, cp1Y: %.1f,  cp2X: %.1f, cp2Y: %.1f, ",
		    cs->cp1.x, cs->cp1.y, cs->cp2.x, cs->cp2.y );
	putc('\n',out);
    }
    doIndent(out,indent+4);
    fprintf( out, "dropRightMillibars: %d, dropLeftMillibars: %d\n",
	    cs->drop_right_millibars, cs->drop_left_millibars );
    doIndent(out,indent+4);
    fprintf( out, "joinRightMillibars: %d, joinLeftMillibars: %d\n",
	    cs->join_right_millibars, cs->join_left_millibars );
    doIndent(out,indent+4);
    fprintf( out, "joinRightHand: %d, joinRightMS: %d, joinRightFlags: %x,\n",
	    cs->join_right_hand&HAND_INDIVIDUAL_MASK,
	    cs->join_right_hand&HAND_MINORSET_UP ?
		-((cs->join_right_hand&HAND_MINORSET_OFFSET)>>HAND_MINORSET_SHIFT) :
		 ((cs->join_right_hand&HAND_MINORSET_OFFSET)>>HAND_MINORSET_SHIFT),
	    (cs->join_right_hand&HAND_COPY_MASK)>>24 );
    doIndent(out,indent+4);
    fprintf( out, "joinLeftHand: %d, joinLeftMS: %d, joinLeftFlags: %x,\n",
	    cs->join_left_hand&HAND_INDIVIDUAL_MASK,
	    cs->join_left_hand&HAND_MINORSET_UP ?
		-((cs->join_left_hand&HAND_MINORSET_OFFSET)>>HAND_MINORSET_SHIFT) :
		 ((cs->join_left_hand&HAND_MINORSET_OFFSET)>>HAND_MINORSET_SHIFT),
	    (cs->join_left_hand&HAND_COPY_MASK)>>24 );
    doIndent(out,indent+4);
    fprintf( out, "starX: %.1f, starY: %.1f,\n",
	    cs->star_center.x, cs->star_center.y );
    doIndent(out,indent+4);
    fprintf( out, "outness: " );
    if ( cs->outness==NULL )
	fprintf( out, "null,\n" );
    else {
	putc('[', out );
	for ( i=0; cs->outness[i].millibars!=-1; ++i ) {
	    if ( i!=0 ) fputs(", ",out);
	    fprintf( out, "{ value: %d, millibars: %d }",
		    cs->outness[i].value, cs->outness[i].millibars );
	}
	fputs("],\n", out );
    }
    doIndent(out,indent+4);
    fprintf( out, "startHonour: %d, endHonour: %d,\n",
	    cs->start_honour, cs->end_honour );
    doIndent(out,indent+4);
    fprintf( out, "ifblock: " );
    danceJSOutputBlock(out, cs->ifblock, indent+2 );
    putc('}',out );
}
    
static void danceJSOutputBlock(FILE *out,Block *b,int indent) {
    int i;

    if ( b==NULL ) {
	fputs("null ", out);
return;
    }

    fprintf( out, "{\n" );
    indent += 2;

    doIndent(out,indent);
    fputs( "condition: ", out );
    danceJSOutputCondition(out,b->condition,indent);
    fputs( ",\n", out );

    doIndent(out,indent);
    fputs( "moves: [\n", out );
    for ( i=0; i<b->move_cnt; ++i ) {
	if ( i!=0 ) fputs( ",\n", out );
	danceJSOutputFigureSegment(out,&b->moves[i],indent=2);
    }
    fputs( "],\n", out );

    doIndent(out,indent);
    fputs( "next: ", out );
    danceJSOutputBlock(out,b->next,indent+2);
    fputs( "}", out );
    
}

static void danceJSOutputRole(FILE *out,Person *p,int indent) {

    doIndent(out,indent);
    fprintf( out, "{\n" );
    indent += 2;

    outputEnumProperty(out,"sex",p->sex,sexNames,indent);
    outputEnumProperty(out,"effectiveRole",p->effective_role,roleNames,indent);
    doIndent(out,indent);
    fprintf( out, "figures: " );
    danceJSOutputBlock(out,&p->figures, indent);

    fprintf( out, "}" );
}

static void danceJSOutputDanceContents(FILE *out, Dance *dance, int indent) {
    int i;

    doIndent(out,indent);
    fprintf( out, "{\n" );
    indent += 2;

    outputStringProperty(out,"name",dance->name,indent);
    outputEnumProperty(out,"style",dance->style,styleNames,indent);
    outputEnumProperty(out,"subStyle",dance->substyle,substyleNames,indent);
    outputStringProperty(out,"devisor",dance->author,indent);
    outputIntProperty(out,"yearWritten",dance->year_written,indent);
    outputStringProperty(out,"interpreter",dance->interpreter,indent);
    outputIntProperty(out,"yearInterpreted",dance->year_interpreted,indent);
    outputStringProperty(out,"animator",dance->animator,indent);
    figureAnimationYear(out,dance,indent);
    outputStringProperty(out,"publication",dance->publication,indent);
    outputStringProperty(out,"tags",dance->tags,indent);
    outputStringProperty(out,"sourceUrl",dance->source_url,indent);
    outputStringProperty(out,"htmlNotes",dance->html_notes,indent);
    outputEnumProperty(out,"formation",dance->formation,startFormationNames,indent);
    outputHexProperty(out,"improperMask",dance->improper_mask,indent);
    outputIntProperty(out,"units",dance->units,indent);

    outputIntProperty(out,"progression",dance->progression,indent);
    outputIntProperty(out,"minorsetSpacing",dance->minorset_spacing,indent);
    outputBooleanProperty(out,"showMinorset",dance->show_minorset,indent);
    outputBooleanProperty(out,"isComponent",dance->is_component,indent);
    outputEnumProperty(out,"bandPos",dance->bandPos,bandNames,indent);
    outputStringProperty(out,"backgroundFile",dance->background_file,indent);
    outputIntProperty(out,"textColor",dance->text_color,indent);
    outputIntProperty(out,"countsPerBar",dance->counts_per_bar,indent);
    outputEnumProperty(out,"doHonour",dance->do_honour,honourNames,indent);
    /* Bounding boxes???? */

    outputIntProperty(out,"roleCnt",dance->role_cnt,indent);
    outputIntProperty(out,"callCnt",dance->call_cnt,indent);
    outputIntProperty(out,"partCnt",dance->part_cnt,indent);
    /* Face images ??? */

    doIndent(out,indent);
    fprintf( out, "calls: [\n" );
    for ( i=0; i<dance->call_cnt; ++i ) {
	if ( i!=0 ) fprintf( out, ",\n" );
	danceJSOutputCallList(out,&dance->calls[i],indent+2);
    }
    fprintf( out, "],\n" );

    doIndent(out,indent);
    fprintf( out, "roles: [\n" );
    for ( i=0; i<dance->role_cnt; ++i ) {
	if ( i!=0 ) fprintf( out, ",\n" );
	danceJSOutputRole(out,&dance->roles[i],indent+2);
    }
    fprintf( out, "],\n" );

    outputIntProperty(out,"compCnt",dance->comp_cnt,indent);
    outputIntProperty(out,"choreoCnt",dance->choreo_cnt,indent);
    doIndent(out,indent);
    if ( dance->comp_cnt==0 )
	fprintf( out, "components: null,\n" );
    else {
	fprintf( out, "components: [ " );
	for ( i=0; i<dance->comp_cnt; ++i ) {
	    if ( i!=0 ) fputs(", ", out);
	    fprintf( out, "v%d", dance->components[i]->choreo_pos );
	}
	fprintf( out, "],\n" );
    }
    doIndent(out,indent);
    if ( dance->choreo_cnt==0 )
	fprintf( out, "sequence: null,\n" );
    else {
	fprintf( out, "sequence: [\n" );
	for ( i=0; i<dance->choreo_cnt; ++i ) {
	    if ( i!=0 ) fputs(",\n",out);
	    danceJSOutputChoreography(out,&dance->sequence[i],indent+2);
	}
	fprintf( out, "],\n" );
    }
    doIndent(out,indent-1);
    fprintf( out, "}" );
}

static void danceJSOutputTrailer(FILE *out, Dance *dance) {
    fprintf( out, ");\n" );
    fprintf( out, "}\n" );
}

int danceJSOutputDance(const char *filename, Dance *dance) {
    FILE *out = fopen(filename,"w");
    if ( out==NULL )
return( 0 );

    danceJSOutputHeader(out,dance);
    danceJSOutputDanceContents(out,dance,0);
    danceJSOutputTrailer(out,dance);
    fclose(out);
return( 1 );
}
