#include "views.h"
#include <assert.h>
#include <string.h>

// Bugs: All hand clasps are "above"
// no audio

#define ARM_COLOR	"#ffc800"

void danceMetadata(FILE *out, Figure *figure) {
    char *form, *improper=" Proper";
    int mixer = 0, USA=0;
    char improperbuffer[100];

    form="Custom";
    switch ( figure->formation ) {
      case sp_squaredSet:
	form = "Square";
      break;
      case sp_rect6Set:
	form = "6 Cpl Rectangle";
      break;
      case sp_dupleMinor:
	form = "Duple Minor";
      break;
      case sp_becket:
	form = "Duple Minor Becket";
      break;
      case sp_becketCCW:
	form = "Duple Minor Becket CCW";
      break;
      case sp_shortWaves:
	form = "Duple Minor Short Waves";
      break;
      case sp_dupleMinorProgressed:
	form = "Duple Minor Progressed";
      break;

      case sp_threeFaceThree:
	form = "Three face Three";
      break;
      case sp_fourFaceFour:
	form = "Four face Four";
      break;
      case sp_tempest:
	form = "Tempest";
      break;
      case sp_quadrupleMinorBecket:
	form = "Quadruple Minor Becket";
      break;

      case sp_tripleMinor:
	form = "Triple Minor";
      break;

      case sp_2Couple:
	form = "2 Cpl Longways";
      break;
      case sp_3Couple:
	form = "3 Cpl Longways";
      break;
      case sp_4Couple:
	form = "4 Cpl Longways";
      break;
      case sp_5Couple:
	form = "5 Cpl Longways";
      break;
      case sp_6Couple:
	form = "6 Cpl Longways";
      break;
      case sp_7Couple:
	form = "7 Cpl Longways";
      break;
      case sp_8Couple:
	form = "8 Cpl Longways";
      break;
      case sp_facingCouples:
	form = "2 Cpl Facing";
      break;

      case sp_3CplCircle:
	form = "3 Cpl Circle";
      break;
      case sp_5CplCircle:
	form = "5 Cpl Circle";
      break;
      case sp_6CplCircle:
	form = "6 Cpl Circle";
      break;
      case sp_7CplCircle:
	form = "7 Cpl Circle";
      break;
      case sp_square3:
	form = "Square of Trios";
      break;

      case sp_scottishDupleMinor:
	form = "2 Cpl dance in 4 Cpl set";
      break;
      case sp_scottishTripleMinor:
	form = "3 Cpl dance in 4 Cpl set";
      break;
      case sp_circularMonople: case sp_trioCircularMonople:
	form = "Circle";
      break;
      default:
      case sp_custom:
	form = "Custom";
      break;
      case sp_custom3:
	form = "Custom Trio";
      break;
    }
    if ( strcmp(figure->name,"Impropriety")==0 )
	improper = "Impropriety";
    else if ( figure->formation==sp_squaredSet || figure->formation==sp_trioCircularMonople )
	improper = "";
    else if ( figure->improper_mask!=0 ) {
	if (( figure->formation==sp_dupleMinor || figure->formation==sp_dupleMinorProgressed || figure->formation==sp_shortWaves ) &&
		(figure->improper_mask==1 || figure->improper_mask==2)) {
	    if ( figure->improper_mask==1 )
		improper = "Improper";
	    else
		improper = "Indecent";
	} else if (( figure->formation==sp_becket || figure->formation==sp_becketCCW ) &&
		(figure->improper_mask==1 || figure->improper_mask==2)) {
	    if ( figure->improper_mask==1 )
		improper = "";
	    else
		improper = "Indecent";
	} else if ( (figure->formation==sp_fourFaceFour || figure->formation==sp_tempest || figure->formation==sp_quadrupleMinorBecket)
		&& figure->improper_mask==3 ) {
	    improper = "";
	} else {
	    char *pt = improperbuffer;
	    int k,any=0;

	    for ( k=0; roleNames[k]!=NULL && k<32; ++k ) {
		if ( figure->improper_mask&(1<<k) ) {
		    if ( any ) {
			*pt++ = ',';
			*pt++ = ' ';
		    }
		    strcpy(pt,roleNames[k]);
		    pt += strlen(pt);
		    any = true;
		}
	    }
	    if ( any ) {
		strcpy(pt, " Improper" );
		improper = improperbuffer;
	    } else
		improper = "";
	}
    } 

    mixer = USA = 0;
    if ( strcmp(figure->name,"Bring Me Flowers")==0 ) {
	form="3&nbsp;Cpl/5&nbsp;Cpl Circle";
	improper="";
	mixer = 1;
    } else if ( strcmp(figure->name,"Turning by Threes")==0 ) {
	form="3 Cpl Circle";
	improper="";
	mixer = 1;
    } else if ( strcmp(figure->name,"Wibsey Roundabout")==0 ) {
	form="5 Cpl Circle";
	improper="";
	mixer = 1;
    } else if ( strcmp(figure->name,"Winter Solstice")==0 ) {
	improper="";
	form="5 Cpl Centered Square";
    } else if ( strcmp(figure->name,"Virginia Reel")==0 )
	form="5-8 Cpl Longways Proper";
    else if ( strcmp(figure->name,"Levi Jackson Rag")==0 || strcmp(figure->name,"Levi Jackson")==0) {
	form="5 Cpl Custom";
	improper="";
	mixer = 1;
    } else if ( strcasecmp(figure->name,"I Care Not for These Ladies")==0 ) {
	form="Circle";
	improper="";
	mixer = 1;
    }
    if ( figure->tags!=NULL ) {
	if (  strcasestr(figure->tags,"USA")!= NULL )
	    USA = 1;
	if ( strcasestr(figure->tags,"mixer")!= NULL )
	    mixer = 1;
    }
    if ( *improper=='\0' )
	fprintf( out, "It is a %s", form );
    else if ( figure->formation==sp_dupleMinor || figure->improper_mask==0 ) {
	if ( *improper=='I' )
	    fprintf( out, "It is an %s %s", improper, form );
	else
	    fprintf( out, "It is a %s %s", improper, form );
    } else
	fprintf( out, "It is a %s with the %s", form, improper );
    if ( mixer )
	fprintf( out, ", a mixer" );
    if ( USA )
	fprintf( out, ", a USA" );
    fprintf( out, ". " );
}

static void danceSVGBasicDancers(DanceView *dv,int width) {
    int i;
    Dance *dance = dv->dance;

    /* place all dancers centered at 0,0 with an orientation of 0 degrees */
    /* This gives us a consistent starting point for transformation matrices later */
    for ( i=0; i<dance->people; ++i ) {
	struct faceImage *fi=NULL;
	fprintf( dv->svg, "\t  <g id=\"D%d\" stroke-width=\"%d\" fill=\"", i, width/12 );
	if ( (i/2)%64<8 ) {
	    int col;
	    danceGetCoupleColor((i/2)%64,NULL,&col);
	    fprintf( dv->svg, "#%06x\" >\n", col );
	} else
	    fprintf( dv->svg, "url(#fillPat%d)\" >\n", (i/2)%64 );
	if ( dance->faceImages!=NULL ) {
	    int j;
	    for ( j=0; j<dance->face_cnt; ++j ) {
		if ( j==dance->faceImages[j].person ) {
		    fi = &dance->faceImages[j];
	    break;
		} else if ( dance->dancers[i].sex==dance->faceImages[j].sex )
		    fi = &dance->faceImages[j];
	    }
	}
	if ( fi==NULL || !fi->hide_normal ) {
	    fprintf( dv->svg, "\t    <circle cx=\"%d\" cy=\"%d\" r=\"%d\" />\n", 0, (int) (-.22*width), width/4 );
	    if ( dance->dancers[i].sex == s_male )
		fprintf( dv->svg, "\t    <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" />\n", -width/2, -width/4, width, width/2 );
	    else
		fprintf( dv->svg, "\t    <ellipse cx=\"%d\" cy=\"0\" rx=\"%d\" ry=\"%d\" />\n", 0, width/2, width/4 );
	}
	if ( fi!=NULL ) {
	    double scale=fi->facescale;
	    fprintf( dv->svg, "\t    <image href=\"%s\" x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" />\n",
		fi->facename,
		(int) (-width*scale/2), (int) (-width*scale/2),
		(int) (width*scale), (int) (width*scale) );
	}
	fprintf( dv->svg, "\t  </g>\n" );
    }
}

static void danceSVGShowBand(DanceView *dv) {
    enum band bandPos = dv->dance->bandPos;
    char *x, *y;
    if ( bandPos==band_unspecified ) {
	if ( dv->dance->style==ds_square || dv->dance->formation==sp_squaredSet )
	    bandPos = band_bottom;
	else
	    bandPos = band_left;
	/* If enum band were zero based this would just be (bandPos+2)%4, but it's 1 based */
	if ( dv->womensOrientation )
	    bandPos = (bandPos+1)%4 +1;
    }
    y="1"; x="98";
    switch ( bandPos ) {
      case band_bottom:
	y="96";
      case band_top:
	fprintf( dv->svg, "  <text class=\"band\" x=\"50%%\" y=\"%s%%\">Band</text>\n", y );
      break;


      case band_left:
      default:
	x="2";
      case band_right:
	fprintf( dv->svg, "  <text class=\"band\" x=\"%s%%\" y=\"44%%\">B</text>\n", x );
	fprintf( dv->svg, "  <text class=\"band\" x=\"%s%%\" y=\"47%%\">a</text>\n", x );
	fprintf( dv->svg, "  <text class=\"band\" x=\"%s%%\" y=\"50%%\">n</text>\n", x );
	fprintf( dv->svg, "  <text class=\"band\" x=\"%s%%\" y=\"53%%\">d</text>\n", x );
      break;
    }
}

void danceSVGInit(DanceView *dv, struct video_options *options) {
    Dance *dance = dv->dance;
    char *back;
    int i, top, width, square_size;
    int _16x9;
    int w;

    if ( dv->svg!=NULL )
return;
    dv->svg= fopen(dv->filename,"w");
    if ( dv->svg==NULL ) {
	fprintf( stderr, "Failed to open output file: %s\n", dv->filename );
exit(1);
    }

    fprintf( dv->svg, "<!DOCTYPE html>\n" );
    fprintf( dv->svg, "<html>\n" );
    fprintf( dv->svg, "<head>\n" );
    fprintf( dv->svg, "  <meta charset=\"UTF-8\" />\n" );
    fprintf( dv->svg, "  <link rel=\"stylesheet\" href=\"assets/dancesvg.css\" type=\"text/css\" />\n" );
    fprintf( dv->svg, "  <script type=\"text/javascript\" src=\"assets/dancesvg.js\" ></script>\n" );
    if ( dv->height==dv->width )
	_16x9 = -1;
    else if ( dv->height*100/dv->width!=56 ) {
	_16x9=0;
	fprintf( dv->svg, "  <style type=\"text/css\">\n" );
	fprintf( dv->svg, "    .aspect-box {\n" );
	fprintf( dv->svg, "      height: 0;\n" );
	fprintf( dv->svg, "      padding-top: %.2f%%;\n", 100.0*dv->height/dv->width );
	fprintf( dv->svg, "      background: #fff0e0;\n" );
	fprintf( dv->svg, "      position: relative;\n" );
	fprintf( dv->svg, "    }\n" );
	fprintf( dv->svg, "  </style>\n" );
    } else
	_16x9 = 1;

    if ( dance->name!=NULL ) {
	fprintf( dv->svg, "  <title>" );
	entityOutput(dv->svg,dance->name,true);
	fprintf( dv->svg, "</title>\n" );
    }
    fprintf( dv->svg, "</head>\n" );
    fprintf( dv->svg, "<body>\n" );
    if ( dance->name!=NULL ) {
	fprintf( dv->svg, "  <h1>" );
	entityOutput(dv->svg,dance->name,true);
	fprintf( dv->svg, "</h1>\n" );
    }
    fprintf( dv->svg, "  <p>This is" );
    if ( dance->style == ds_english || dance->style == ds_acd )
	fprintf( dv->svg, " an " );
    else
	fprintf( dv->svg, " a " );
    if ( dv->dance->style==ds_english && ((dv->dance->year_written<1850 && dv->dance->year_written>0 ) ||
					    (dv->dance->year_written>-1850 && dv->dance->year_written<0 )))
	fprintf( dv->svg,"Engliſh Country Dance, ");
    else if ( dv->dance->style==ds_contredanse && ((dv->dance->year_written<1850 && dv->dance->year_written>0 ) ||
					    (dv->dance->year_written>-1850 && dv->dance->year_written<0 )))
	fprintf( dv->svg,"Contredanſe, ");
    else
	fprintf( dv->svg, "%s, ", styleNames[dance->style]);
    if ( dance->author!=NULL ) {
	char *word;
	if ( strcasestr(dance->author,"Playford")!=NULL ) {
	    if ( dance->year_written==1651 )
		fprintf( dv->svg, "first found in Playford's Dancing Engliſh Maſter" );
	    else
		fprintf( dv->svg, "first found in Playford's Dancing Maſter" );
	    word = "of";
	} else if ( strcasecmp(dance->author,"Walſh")==0 ||
		(dance->publication!=NULL && strcasecmp(dance->author,dance->publication)==0 ) ||
		strcasestr(dance->author,"Compleat")!=NULL ||
		strcasecmp(dance->author,"Thompſon")==0 ) {
	    fprintf( dv->svg, "first found in %s", dance->author );
	    word = "of";
	} else {
	    fprintf( dv->svg, "devised by %s", dance->author );
	    word = "in";
	}
	if ( dance->year_written>0 )
	    fprintf( dv->svg, " %s %d. ", word, dance->year_written );
	else if ( dance->year_written<0 )
	    fprintf( dv->svg, " %s ~%d. ", word, -dance->year_written );
	else
	    fprintf( dv->svg, ". " );
    } else if ( dance->year_written!=0 ) {
	if ( dance->year_written>0 )
	    fprintf( dv->svg, " devised in %d. ", dance->year_written );
	else if ( dance->year_written<0 )
	    fprintf( dv->svg, " devised in ~%d. ", -dance->year_written );
    }
    if ( dance->publication!=NULL && (dance->author==NULL || strcasecmp(dance->author,dance->publication)!=0 ))
	fprintf( dv->svg, "Found in %s. ", dance->publication );
    else if ( dance->tags!=NULL ) {
	if ( strcasestr(dance->tags,"playford assembly supplement")!= NULL )
	    fprintf( dv->svg, "Found in The Playford Assembly Supplement. " );
	else if ( strcasestr(dance->tags,"playford assembly")!= NULL )
	    fprintf( dv->svg, "Found in The Playford Assembly. " );
	if ( strcasestr(dance->tags,"playford ball")!= NULL )
	    fprintf( dv->svg, "Found in The Playford Ball. " );
	if ( strcasestr(dance->tags,"cracking chestnuts")!= NULL )
	    fprintf( dv->svg, "Found in Cracking Chestnuts. " );
    }
    if ( dance->interpreter!=NULL && *dance->interpreter!='\0' ) {
	fprintf( dv->svg, "Adapted by %s", dance->interpreter );
	if ( dance->year_interpreted>0 )
	    fprintf( dv->svg, " in %d. ", dance->year_interpreted );
	else if ( dance->year_interpreted<0 )
	    fprintf( dv->svg, " in ~%d. ", -dance->year_interpreted );
	else
	    fprintf( dv->svg, ". " );
    }
    danceMetadata(dv->svg, dance);
    fprintf( dv->svg, "</p>\n" );
    if ( _16x9==1 )
	fprintf( dv->svg, "  <div class=\"aspect-16-9-box\" >\n" );
    else if ( _16x9==-1 )
	fprintf( dv->svg, "  <div class=\"aspect-square-box\" >\n" );
    else
	fprintf( dv->svg, "  <div class=\"aspect-box\" >\n" );
    back = options->background;
    if ( back==NULL )
	back = dance->background_file;
    if ( back!=NULL && strchr(back,'%')!=NULL )
	back = NULL;
    if ( back==NULL )
	fprintf( dv->svg, "    <div id=\"videodiv\" class=\"inside-aspect-box\" >\n" );
    else if ( *back=='#' )
	fprintf( dv->svg, "    <div id=\"videodiv\" class=\"inside-aspect-box\" style=\"background: %s\" >\n", back );
    else
	fprintf( dv->svg, "    <div id=\"videodiv\" class=\"inside-aspect-box\" style=\"background-image: url('%s')\" >\n", back );
    
    fprintf( dv->svg, "      <svg id=\"videosvg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"100%%\" height=\"100%%\" viewBox=\"0 0 %d %d\" >\n", dv->width, dv->height );

    danceSVGShowBand(dv);	/* Always in the same place for all frames */

    fprintf( dv->svg, "\t<defs>\n" );

    {
	double oldscale = dv->scale;
	dv->scale = dv->full_scale;
	width = dancePersonWidth(dv);
	dv->scale = oldscale;
    }

    square_size = (width/8+1)&(~1);
    top = dv->couples;
    if ( top>8*8 )
	top = 8*8;
    for ( i=8; i<top; ++i ) {
	int col1i = (i-8)/7, col2i =  (i-8)%7;
	int col1, col2;
	if ( col2i>=col1i )
	    ++col2i;
	danceGetCoupleColor(col1i,NULL,&col1);
	danceGetCoupleColor(col2i,NULL,&col2);
	
	fprintf( dv->svg, "\t  <pattern id=\"fillPat%d\" x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" patternUnits=\"userSpaceOnUse\" >\n", i, square_size, square_size );
	fprintf( dv->svg, "\t    <rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, col1 );
	fprintf( dv->svg, "\t    <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, square_size/2, square_size/2, col1 );
	fprintf( dv->svg, "\t    <rect x=\"0\" y=\"%d\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, square_size/2, col2 );
	fprintf( dv->svg, "\t    <rect x=\"%d\" y=\"0\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, square_size/2, col2 );
	fprintf( dv->svg, "\t  </pattern>\n" );
    }
    top = dance->role_cnt/2;
    if ( top>8*8 )
	top = 8*8;
    for ( i=8; i<top; ++i ) {
	int col1i = (i-8)/7, col2i =  (i-8)%7;
	int col1, col2;
	if ( col2i>=col1i )
	    ++col2i;
	danceGetCoupleColor(col1i,&col1,NULL);
	danceGetCoupleColor(col2i,&col2,NULL);
	
	fprintf( dv->svg, "\t  <pattern id=\"strokePat%d\" x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" patternUnits=\"userSpaceOnUse\" >\n", i, square_size, square_size );
	fprintf( dv->svg, "\t    <rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, col1 );
	fprintf( dv->svg, "\t    <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, square_size/2, square_size/2, col1 );
	fprintf( dv->svg, "\t    <rect x=\"0\" y=\"%d\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, square_size/2, col2 );
	fprintf( dv->svg, "\t    <rect x=\"%d\" y=\"0\" width=\"%d\" height=\"%d\" style=\"stroke: none; fill: #%06x;\" />\n", square_size/2, square_size/2, square_size/2, col2 );
	fprintf( dv->svg, "\t  </pattern>\n" );
    }

    fprintf( dv->svg, "\t</defs>\n" );
    w = 2*round(dv->dance->units/200*dv->scale);
    if ( w==0 ) w=2;
    fprintf( dv->svg, "\t<g id=\"display\" fill=\"none\" stroke-width=\"%d\" stroke=\"" ARM_COLOR "\" >\n", w );

    danceSVGBasicDancers(dv,width);

    fprintf( dv->svg, "\t  <g id=\"dummy\">\n" );
    fprintf( dv->svg, "\t  </g>\n" );
    fprintf( dv->svg, "\t</g>\n" );

    fprintf( dv->svg, "      </svg>\n" );
    fprintf( dv->svg, "    </div>\n" );
    fprintf( dv->svg, "    <div id=\"frame\" class=\"bottom-box\"></div>\n" );
    fprintf( dv->svg, "  </div>\n" );

    dv->svg_dancers = calloc(dance->people,sizeof(struct dinfo));
}

static void formatMatrix(char *matrixbuf, float matrix[6], int force) {
    char *mpt;
    int i;

    for ( i=0; i<6; ++i ) {
	matrix[i] = round(matrix[i]*100)/100.0;
	if ( matrix[i]==0 )
	    matrix[i]=0;		/* Get rid of negative 0, I hope */
    }
    mpt = matrixbuf;
    for ( i=0; i<6; ++i ) {
	if ( matrix[i]==(int) matrix[i] )
	    sprintf( mpt, "%.0f ", matrix[i]);
	else
	    sprintf( mpt, "%.2f ", matrix[i]);
	mpt += strlen(mpt);
    }
    if ( mpt[-1]==' ' )
	--mpt;
    *mpt = '\0';
}

static void danceSVGAddClasp3(DanceView *dv,int x1,int y1,int xmid,int ymid,int x2, int y2,int debug) {
    char buffer[200];

    if ( dv->svg_clasp_cnt >= dv->svg_clasp_max )
	dv->svg_clasp = realloc(dv->svg_clasp, (dv->svg_clasp_max += 2*dv->dance->people)*sizeof(char *));
    sprintf( buffer, "%d,%d %d,%d %d,%d", x1, y1, xmid, ymid, x2, y2 );
    dv->svg_clasp[dv->svg_clasp_cnt++] = strdup(buffer);
}

static void danceSVGAddClasp2(DanceView *dv,int x1,int y1,int x2, int y2,int debug) {
    char buffer[200];

    if ( dv->svg_clasp_cnt >= dv->svg_clasp_max )
	dv->svg_clasp = realloc(dv->svg_clasp, (dv->svg_clasp_max += 2*dv->dance->people)*sizeof(char *));
    sprintf( buffer, "%d,%d %d,%d", x1, y1, x2, y2 );
    dv->svg_clasp[dv->svg_clasp_cnt++] = strdup(buffer);
}

static void danceSVGRecordClasp(DanceView *dv, int h1, int h2) {
    /* there is a hand clasp between the person in h1 and the person in h2 */
    /*  the bit masks in h1 and h2 will say which hand each is using */

    if ( (h1&HAND_PERSON_MASK)>dv->dance->people  )
return;
    if ( dv->dont_show_couples_above_me>0 &&
	    (h1&HAND_PERSON_MASK)>=2*dv->dont_show_couples_above_me  ) {
	/* hands are measured in people and there are two people to a couple */
return;
    }

    int person_width = dancePersonWidth(dv);
    /*int handClaspDiam = person_width*HAND_FRAC;*/
    Person *p1 = &dv->dance->dancers[(h1&HAND_PERSON_MASK)];
    float degrees1 = p1->now_orientation;
    double c1 = -cos(degrees1*PI/180.0);
    double s1 = sin(degrees1*PI/180.0);
    int x1,x2,x3,y1,y2,y3;
    double factor;
    int debug=0;

    factor=1.0;
    /* Women are elipses, so moving the arms up, or back means moving the shoulders in a bit */
    if ( p1->sex==s_female && (h2&(HAND_FRONT_MASK|HAND_BACK_MASK)) )
	factor = .8;
    if ( h1&HAND_LEFT_MASK ) {
	x1 = dv->xorigin + p1->now.x*dv->scale + factor*c1*person_width/2;
	y1 = dv->yorigin + p1->now.y*dv->scale - factor*s1*person_width/2;
    } else {
	x1 = dv->xorigin + p1->now.x*dv->scale - factor*c1*person_width/2;
	y1 = dv->yorigin + p1->now.y*dv->scale + factor*s1*person_width/2;
    }
    /* p->now is in the center of the body. x1,y1 place the shoulder in the middle of the side */
    if ( h2&HAND_FRONT_MASK ) {
	/* Move the shoulder up to the front edge of the body */
	x1 -= s1*person_width/4;
	y1 -= c1*person_width/4;
    } else if ( h2&HAND_BACK_MASK ) {
	/* Or the back edge */
	x1 += s1*person_width/4;
	y1 += c1*person_width/4;
    }

    if ( (h2&HAND_PERSON_MASK)==HAND_STAR ) {
	x2 = dv->xorigin + p1->now_star_center.x*dv->scale;
	y2 = dv->yorigin + p1->now_star_center.y*dv->scale;
    } else {
	int ballroom = (h2&HAND_BALLROOM_MASK);
	if ( (h2&HAND_INDIVIDUAL_MASK)>dv->dance->people )
return;
	if ( dv->dont_show_couples_above_me>0 && (
		(h2&HAND_INDIVIDUAL_MASK)>=2*dv->dont_show_couples_above_me) )
return;
	Person *p2 = &dv->dance->dancers[(h2&HAND_INDIVIDUAL_MASK)];
	float degrees2;
	double c2;
	double s2;

	if ( (h2&HAND_INDIVIDUAL_MASK)<(h1&HAND_INDIVIDUAL_MASK) ) {
	    debug=1;
	}
	degrees2 = p2->now_orientation;
	c2 = -cos(degrees2*PI/180.0);
	s2 = sin(degrees2*PI/180.0);
	factor=1.0;
	/* Women are elipses, so moving the arms up, or back means moving the shoulders in a bit */
	if ( p2->sex==s_female && (h2&(HAND_FRONT_MASK|HAND_BACK_MASK)) )
	    factor = .8;
	if ( h2&HAND_LEFT_MASK ) {
	    x2 = dv->xorigin + p2->now.x*dv->scale + factor*c2*person_width/2;
	    y2 = dv->yorigin + p2->now.y*dv->scale - factor*s2*person_width/2;
	} else {
	    x2 = dv->xorigin + p2->now.x*dv->scale - factor*c2*person_width/2;
	    y2 = dv->yorigin + p2->now.y*dv->scale + factor*s2*person_width/2;
	}
	if ( h2&HAND_FRONT_MASK ) {
	    /* Move the shoulder up to the front edge of the body */
	    x2 -= s2*person_width/4;
	    y2 -= c2*person_width/4;
	} else if ( h2&HAND_BACK_MASK ) {
	    /* Or the back edge */
	    x2 += s2*person_width/4;
	    y2 += c2*person_width/4;
	}
	if ( ballroom ) {
	    if ( (h2&HAND_LEFT_MASK) && (h1&HAND_RIGHT_MASK) ) {
		Person *p = p2;
		int h = h2;
		double s=s2,c=c2;
		p2 = p1; h2=h1;
		p1 = p;  h1=h;
		s2 = s1; c2 = c1;
		s1 = s; c1 = c;
	    }
	    if ( (h1&HAND_LEFT_MASK) && (h2&HAND_RIGHT_MASK) ) {
		x3 = dv->xorigin + (p1->now.x*dv->scale + 3*c1*person_width/2 +
				    p2->now.x*dv->scale - 4*c2*person_width/2)/2;
		y3 = dv->yorigin + (p1->now.y*dv->scale - 3*s1*person_width/2 +
				    p2->now.y*dv->scale + 4*s2*person_width/2)/2;
		danceSVGAddClasp3(dv,x1,dv->height-y1,x3,dv->height-y3,x2,dv->height-y2,debug);
return;
	    }
	} else if ( h2&HAND_OUT_MASK ) {
	    double unitx, unity, len;
	    if ( h2&HAND_RIGHT_MASK ) {
		unitx = x1-x2;
		unity = y1-y2;
	    } else {
		unitx = x2-x1;
		unity = y2-y1;
	    }
	    len = sqrt(unitx*unitx+unity*unity);
	    unitx /= len; unity/=len;
	    len *= p1->outness.now/100.0;
	    x3 = (x1+x2)/2 + unity*len;
	    y3 = (y1+y2)/2 - unitx*len;
	    danceSVGAddClasp3(dv,x1,dv->height-y1,x3,dv->height-y3,x2,dv->height-y2,debug);
return;	    
	} else if ( h2&(HAND_FRONT_MASK|HAND_BACK_MASK) ) {
	    double unitx, unity, len;
	    unitx = x2-x1;
	    unity = y2-y1;
	    len = sqrt(unitx*unitx + unity*unity);
	    unitx /= len;
	    unity /= len;

	    len = person_width/6;
	    if ( h2&HAND_FRONT_MASK )
		len = -len;
	    if ( dancerOnTheRight(p1,p2) )
		len = -len;
	    x3 = (x1+x2)/2.0 - unity*len;
	    y3 = (y1+y2)/2.0 + unitx*len;
	    danceSVGAddClasp3(dv,x1,dv->height-y1,x3,dv->height-y3,x2,dv->height-y2,debug);
return;
	}
    }
    danceSVGAddClasp2(dv,x1,dv->height-y1,x2,dv->height-y2,debug);
}

static void danceSVGRecordDancer(DanceView *dv,Person *dancer) {
    float degrees = dancer->now_orientation;
    double c = cos(degrees*PI/180.0);
    double s = -sin(degrees*PI/180.0);
    int ewhich;
    int x = dv->xorigin + dancer->now.x*dv->scale;
    int y = dv->yorigin + dancer->now.y*dv->scale;
    /*int levitation_len = 1.5*person_width * dancer->levitation.now/100.0;*/
    float my_honour = dancer->now_honour;
    float scale;
    float matrix[6];
    char matrixbuf[100];
    int i;

#if 0	/* No support for this */
    if ( dv->dont_show_couples_above_me>0 &&
	    dancer->couple_number > dv->dont_show_couples_above_me )
return;
#endif


    if ( my_honour==0 )
	my_honour = dv->dance->honour;

    scale = (1+my_honour) * dv->scale / dv->full_scale;

    matrix[0] = c * scale;
    matrix[1] = s * scale;
    matrix[2] =-s * scale;
    matrix[3] = c * scale;
    matrix[4] = x;
    matrix[5] = dv->height - y;

    if ( dv->dance->is_figure )
	ewhich = dancer->couple_number-1;
    else
	ewhich = role2couple[dancer->effective_role]-1;
    if ( ewhich<0 )
	ewhich = -1;

    for ( i=0; i<dv->svg_roles_used_cnt; ++i )
	if ( dv->svg_roles_used[i]==ewhich )
    break;
    if ( i>=dv->svg_roles_used_cnt )
	dv->svg_roles_used[ dv->svg_roles_used_cnt++ ] = ewhich;
    ewhich = i;

#if 0
    if ( ewhich<0 )
	strcpy(strokebuf,"#000000");
    else if ( ewhich<8 ) {
	int col;
	danceGetCoupleColor(which,&col,NULL);
	sprintf(strokebuf,"#%06x", col );
    } else
	sprintf(strokebuf,"url(#strokePat%d)", which );
#endif

    formatMatrix(matrixbuf,matrix,false);

    i = dancer - dv->dance->dancers;
    dv->svg_dancers[i].matrix = strdup(matrixbuf);
    dv->svg_dancers[i].role_index = ewhich;
}

void danceSVGRenderFrame(DanceView *dv, int vframe) {
    int i;

    for ( i=0; i<dv->dance->people; ++i )
	free( dv->svg_dancers[i].matrix );
    for ( i=0; i<dv->svg_clasp_cnt; ++i )
	free( dv->svg_clasp[i]);
    dv->svg_clasp_cnt = 0;

#if 0
    if ( vframe>=dv->dance->whirl_bar*dv->frames_per_bar &&
	    vframe<dv->dance->whirl_end_bar*dv->frames_per_bar )
	ButterflyWhirlBackground(dv,vframe);
#endif

    if ( dv->dance->style != ds_longsword ) for ( i=0; i<dv->dance->people; ++i ) {
	Person *dancer = &dv->dance->dancers[i];
	if ( dancer->now_hand_right!=0 && (dancer->now_hand_right&HAND_INDIVIDUAL_MASK)>=i )
	    danceSVGRecordClasp(dv,(i|HAND_RIGHT_MASK),dancer->now_hand_right);
	if ( dancer->now_hand_left!=0 && (dancer->now_hand_left&HAND_INDIVIDUAL_MASK)>=i )
	    danceSVGRecordClasp(dv,(i|HAND_LEFT_MASK),dancer->now_hand_left);
    }
    for ( i=0; i<dv->dance->people; ++i )
	danceSVGRecordDancer(dv,&dv->dance->dancers[i]);
    if ( dv->dance->style != ds_longsword ) {
	for ( i=0; i<dv->dance->people; ++i ) {
	    Person *dancer = &dv->dance->dancers[i];
	    if ( ((dancer->now_hand_right&HAND_ABOVE_MASK)!=0 && (dancer->now_hand_right&HAND_INDIVIDUAL_MASK)>=i ) ||
		    (dancer->now_hand_right!=0 && dv->debugClasp ))
		danceSVGRecordClasp(dv,(i|HAND_RIGHT_MASK),dancer->now_hand_right);
	    if ( ((dancer->now_hand_left&HAND_ABOVE_MASK)!=0 && (dancer->now_hand_left&HAND_INDIVIDUAL_MASK)>=i ) ||
		    (dancer->now_hand_left!=0 && dv->debugClasp ))
		danceSVGRecordClasp(dv,(i|HAND_LEFT_MASK),dancer->now_hand_left);
	}
    }
}

void danceSVGWriteVideoFrame(DanceView *dv) {
    int i, base;
    char *call;

    if ( dv->svg_vframe>=dv->svg_vframe_max ) {
	dv->svg_vframe_max   += 300*dv->frames_per_sec;	/* 5 minutes of frames should do */
	dv->svg_dancer_frames = realloc( dv->svg_dancer_frames,
		dv->svg_vframe_max*dv->dance->people*sizeof(struct dinfo));
	dv->svg_call_frames   = realloc( dv->svg_call_frames, dv->svg_vframe_max*sizeof(int));
	dv->svg_clasp_frames  = realloc( dv->svg_clasp_frames, dv->svg_vframe_max*sizeof(char **));
    }

    base = dv->svg_vframe*dv->dance->people;
    for ( i=0; i<dv->dance->people; ++i ) {
	dv->svg_dancer_frames[base+i].matrix = strdup(dv->svg_dancers[i].matrix);
	dv->svg_dancer_frames[base+i].role_index = dv->svg_dancers[i].role_index;
    }

    if ( dv->dance->current==NULL )
	call = "";
    else
	call = dv->dance->current->callName;
    for ( i=0; i<dv->svg_calls_used_cnt; ++i )
	if ( strcmp(dv->svg_calls_used[i],call)==0 )
    break;
    if ( i>=dv->svg_calls_used_cnt ) {
	if ( i>=dv->svg_calls_used_max )
	    dv->svg_calls_used = realloc(dv->svg_calls_used,(dv->svg_calls_used_max+=32)*sizeof(char *));
	dv->svg_calls_used[i]= strdup(call);
	++dv->svg_calls_used_cnt;
    }
    dv->svg_call_frames[dv->svg_vframe] = i;

    if ( dv->svg_clasp_cnt==0 )
	dv->svg_clasp_frames[dv->svg_vframe] = NULL;
    else {
	char **clasps;
	clasps = malloc((dv->svg_clasp_cnt+1)*sizeof(char *));
	for ( i=0; i<dv->svg_clasp_cnt; ++i )
	    clasps[i] = strdup( dv->svg_clasp[i] );
	clasps[i] = NULL;
	dv->svg_clasp_frames[dv->svg_vframe] = clasps;
    }

    ++dv->svg_vframe;
}
    
void danceSVGFinish(DanceView *dv, struct video_options *options) {
    Dance *dance = dv->dance;
    int i,j,k,bars;
    int people;
    time_t now;
    struct tm *tm;
    int year;

    fprintf( dv->svg, "  <div id=\"callName\" class=\"call\"></div>\n" );

    fprintf( dv->svg,"  <table class=\"controls\" >\n" );
    fprintf( dv->svg,"    <tr><td><img id=\"playstop\" src=\"assets/play.png\" onclick=\"startStopPlay();\"></td></tr>\n" );
    fprintf( dv->svg,"  </table>\n" );
    /* Put more controls here !!!!!! */
    if ( dance->html_notes!=NULL )
	fprintf( dv->svg, "%s\n", dance->html_notes );

    time(&now);
    tm = localtime(&now);
    year = tm->tm_year + 1900;
    if (( dance->year_written<year-95 && dance->year_written>0 ) ||
	    (-dance->year_written<year-95 && dance->year_written<0 ) ||
	    strcasestr(dance->author,"George Williams")!=NULL || strcasecmp(dance->author,"Traditional")==0 ) {
	bars = 1;
	fprintf( dv->svg, "<table style=\"text-align: left;\">\n" );
	for ( k=0; k<dance->call_cnt; ++k ) {
	    fprintf( dv->svg, "  <tr><td>%d-%d</td><td>",
		    bars, bars+dance->calls[k].millibars/1000-1 );
	    entityOutput(dv->svg,dance->calls[k].callName,2);
	    fprintf( dv->svg, "</td></tr>\n" );
	    bars += dance->calls[k].millibars/1000;
	}
	fprintf( dv->svg, "</table>\n" );
    }
    if ( dance->source_url!=NULL )
	fprintf( dv->svg, "  <p> See <a href=\"%s\">this for an online reference.</a></p>\n",
		dance->source_url );
    if ( dance->animator!=NULL )
	fprintf( dv->svg, "  <p class=\"animation-credit\">%s fecit</p>\n", dance->animator );
    fprintf( dv->svg, "</body>\n" );
    fprintf( dv->svg, "<script type=\"text/javascript\">\n" );
    fprintf( dv->svg, "  frameInterval = %d;\n", 1000/dv->frames_per_sec );
    fprintf( dv->svg, "  frameOriginalInterval = %d;\n", 1000/dv->frames_per_sec );
    fprintf( dv->svg, "  framesPerCount = %d;\n", dv->frames_per_bar/dance->counts_per_bar );

    fprintf( dv->svg, "  callNames = [\n" );
    for ( i=0; i<dv->svg_calls_used_cnt; ++i ) {
	fprintf( dv->svg, "    \"" );
	entityOutput(dv->svg,dv->svg_calls_used[i],2);
	free(dv->svg_calls_used[i]);
	if ( i==dv->svg_calls_used_cnt-1 )
	    fprintf( dv->svg, "\" ];\n" );
	else
	    fprintf( dv->svg, "\",\n" );
    }
    free(dv->svg_calls_used);

    fprintf( dv->svg, "  roleBorders = [\n" );
    for ( i=0; i<dv->svg_roles_used_cnt; ++i ) {
	int ewhich = dv->svg_roles_used[i];
	fprintf( dv->svg, "    \"" );
	if ( ewhich<0 )
	    fprintf( dv->svg, "#000000" );
	else if ( ewhich<8 ) {
	    int col;
	    danceGetCoupleColor(ewhich,&col,NULL);
	    fprintf( dv->svg, "#%06x", col );
	} else
	    fprintf( dv->svg, "url(#strokePat%d)", ewhich );
	if ( i==dv->svg_roles_used_cnt-1 )
	    fprintf( dv->svg, "\" ];\n" );
	else
	    fprintf( dv->svg, "\",\n" );
    }
    /*free(dv->svg_roles_used); */ /* Fixed array */

    people = dv->dance->people;
    fprintf( dv->svg, "  frameDancers = [\n" );
    for ( i=0; i<dv->svg_vframe; ++i ) {
	fprintf( dv->svg, "    [" );
	for ( j=0; j<people; ++j ) {
	    fprintf( dv->svg, "[ \"%s\", %d ]%s",
		    dv->svg_dancer_frames[i*people+j].matrix,
		    dv->svg_dancer_frames[i*people+j].role_index,
		    j==people-1 ? "]" : ", ");
	    free( dv->svg_dancer_frames[i*people+j].matrix );
	}
	if ( i==dv->svg_vframe-1 )
	    fprintf( dv->svg, "];\n" );
	else
	    fprintf( dv->svg, ",\n" );
    }
    free(dv->svg_dancer_frames);

    fprintf( dv->svg, "  frameCalls = [ " );
    for ( i=0; i<dv->svg_vframe; ++i ) {
	fprintf( dv->svg, "%d", dv->svg_call_frames[i]);
	if ( i==dv->svg_vframe-1 )
	    fprintf( dv->svg, " ];\n" );
	else if ( i%20==19 )
	    fprintf( dv->svg, ",\n\t" );
	else
	    fprintf( dv->svg, ", " );
    }
    free(dv->svg_call_frames);

    fprintf( dv->svg, "  frameClasps = [\n" );
    for ( i=0; i<dv->svg_vframe; ++i ) {
	if ( dv->svg_clasp_frames[i]==NULL )
	    fprintf( dv->svg, "\t[]" );
	else {
	    char **clasps = dv->svg_clasp_frames[i];
	    fprintf( dv->svg, "\t[" );
	    for ( j=0; clasps[j]!=NULL; ++j ) {
		fprintf( dv->svg, "\"%s\"", clasps[j] );
		free( clasps[j]);
		if ( clasps[j+1]==NULL )
		    fprintf( dv->svg, "]" );
		else
		    fprintf( dv->svg, ", " );
	    }
	    free(clasps);
	}
	if ( i==dv->svg_vframe-1 )
	    fprintf( dv->svg, "];\n" );
	else
	    fprintf( dv->svg, ",\n" );
    }
    free(dv->svg_clasp_frames);

    fprintf( dv->svg, "  _setFrame(0);\n" );
    fprintf( dv->svg, "</script>\n" );
    fprintf( dv->svg, "</html>\n" );
    fclose( dv->svg ); dv->svg=NULL;

    if ( options->flags & (1<<df_openAfter) ) {
	char buffer[400];
	sprintf( buffer, "open '%s'\n", dv->filename );
	system( buffer );
    }
}
	
    
	
