#include "squaredance.h"
#include <time.h>
#include <assert.h>
#include <gtk/gtk.h>		/* For pixbufs */

void conditionFree(Condition *c) {
    if ( c==NULL )
return;
    conditionFree(c->op1);
    conditionFree(c->op2);
    free(c);
}

void blockFree(Block *b) {
    int i;
    Block *next;
    
    while ( b!=NULL ) {
	for ( i=0; i<b->move_cnt; ++i ) {
	    free(b->moves[i].levitation);
	    free(b->moves[i].outness);
	    free(b->moves[i].rightHand);
	    free(b->moves[i].leftHand);
	    free(b->moves[i].swordPoint);
	    blockFree(b->moves[i].ifblock);
	}
	conditionFree(b->condition);
	next = b->next;
	free(b);
	b = next;
    }
}

void peopleFreeContents(Person *p) {
    int i;
    /* FigureSegments have nothing inside them to free */
    /*  and people have nothing else to free but them */
#if 0		/* Faces are freed in FigureFree because several dancers may share the same face */
    if ( p->face!=NULL ) {
	g_object_unref(G_OBJECT(p->face));
	p->face=NULL;
    }
#endif
    for ( i=0; i<p->figures.move_cnt; ++i ) {
	free(p->figures.moves[i].levitation);
	free(p->figures.moves[i].outness);
	blockFree(p->figures.moves[i].ifblock);
    }
    free( p->figures.moves );
}

void figureFreeCalls(Figure *figure) {
    int i;

    if ( figure->calls!=NULL ) {
	for ( i=0; i<figure->call_cnt; ++i ) {
	    free(figure->calls[i].callName );
	    free(figure->calls[i].audioFilename );
	    free(figure->calls[i].longaudioFilename );
	    free(figure->calls[i].claplist_millibars );
	}
	free( figure->calls );
	figure->calls = NULL;
	figure->call_cnt = 0;
    }
}

void figureFreeRoles(Figure *figure) {
    int i;

    if ( figure->dancers!=NULL ) {
	for ( i=0; i<figure->people; ++i )
	    peopleFreeContents(&figure->dancers[i]);
	free( figure->dancers );
    }
    if ( figure->roles!=NULL ) {
	for ( i=0; i<figure->role_cnt; ++i )
	    peopleFreeContents(&figure->roles[i]);
	free( figure->roles );
    }
    figure->dancers = figure->roles = NULL;
    figure->role_cnt = figure->people = 0;
}

static void segmentFree(MusicSegment *s) {
    MusicSegment *next;

    while ( s!=NULL ) {
	next = s->next;
	free(s->name);
	free(s);
	s = next;
    }
}

static void figureFreeBBs(Figure *figure) {
    BBList *bbs, *next;
    for ( bbs=figure->bbs; bbs!=NULL; bbs = next ) {
	next = bbs->next;
	free(bbs);
    }
    figure->bbs = NULL;
}

void mdFreeContents(MusicDescription *md) {
    free( md->filename );
    segmentFree(md->intro);
    segmentFree(md->minorset);
    segmentFree(md->lastminorset);
    segmentFree(md->body);
    segmentFree(md->ending);
    memset(md,0,sizeof(*md));
}

void mdFree(MusicDescription *md) {
    if ( md!=NULL ) {
	mdFreeContents(md);
	free(md);
    }
}

static void comparisonFree(Comparison *comp) {
    /* Do not free the dance field, that is done by figureFree above */

    figureFree(comp->dances[0].d);
    figureFree(comp->dances[1].d);
    free(comp->dances[0].filename);
    free(comp->dances[1].filename);
    free(comp->display_id);
    free(comp->sourceImage);
    free(comp->imageCopyright);
}

void figureFree(Figure *figure) {
    int i;

    if ( figure==NULL )
return;
    free( figure->name );
    free( figure->altName );
    free( figure->callGroup );
    free( figure->author );
    free( figure->interpreter );
    free( figure->source );
    free( figure->animator );
    free( figure->license );
    free( figure->animation_date );
    free( figure->music_name );
    free( figure->music_author );
    free( figure->titleAudioFilename );
    free( figure->tags );
    free( figure->publication );
    free( figure->background_file );
    free( figure->include_md );

    free( figure->url );
    free( figure->url10 );
    free( figure->url11 );
    free( figure->source_url );
    free( figure->sicilian_url );
    free( figure->orig_url );

    free( figure->notes );
    free( figure->html_notes );

    mdFreeContents(&figure->music);
    figureFreeRoles(figure);
    figureFreeCalls(figure);
    figureFreeBBs(figure);
    for ( i=0 ; i<figure->face_cnt; ++i ) {
	free( figure->faceImages[i].facename );
	if ( figure->faceImages[i].image != NULL )
	    g_object_unref(G_OBJECT(figure->faceImages[i].image));
    }
    free( figure->faceImages );
    free( figure->sequence );
    for ( i=0; i<figure->comp_cnt ; ++i ) {
	figure->components[i]->dancers = NULL;
	figureFree(figure->components[i]);
    }
    free( figure->components );
    free( figure->orderedList );
    if ( figure->is_comparison )
	comparisonFree((Comparison *) figure);
    free( figure );
}

void figureLibFree(FigureLib *lib) {
    int i;

    if ( lib==NULL )
return;
    if ( lib->figures!=NULL ) {
	for ( i=0; lib->figures[i]!=NULL; ++i )
	    figureFree(lib->figures[i]);
    }
    free(lib);
}

Condition *conditionCopy(Condition *c) {
   Condition *ret;
   
   if ( c==NULL )
return( NULL );
   ret = malloc(sizeof(Condition));
   memcpy(ret,c,sizeof(Condition));
   ret->op1 = conditionCopy(ret->op1);
   ret->op2 = conditionCopy(ret->op2);
return( ret );
}

Block *blockCopy(Block *b) {
    Block *ret, *cur, *last=NULL;
    int i;

    if ( b==NULL )
return( NULL );

    while ( b!= NULL ) {
	cur = calloc(1,sizeof(Block));
	if ( last==NULL )
	    ret = cur;
	else
	    last->next = cur;
	last = cur;
	memcpy(cur,b,sizeof(Block));
	cur->moves = malloc(cur->move_max*sizeof(FigureSegment));
	memcpy(cur->moves,b->moves,cur->move_max*sizeof(FigureSegment));
	cur->condition = conditionCopy(b->condition);
	for ( i=0; i<cur->move_cnt; ++i ) {
	    FigureSegment *cs = &cur->moves[i];
	    cs->ifblock = blockCopy(cs->ifblock);
	    cs->levitation = valueChangeCopy(cs->levitation);
	    cs->outness = valueChangeCopy(cs->outness);
	    cs->rightHand = rThetaChangeCopy(cs->rightHand);
	    cs->swordPoint = rThetaChangeCopy(cs->swordPoint);
	    cs->leftHand = rThetaChangeCopy(cs->leftHand);
	}
	b = b->next;
    }
return( ret );
}

int blockCountMillibars(Block *b) {
    int i, mb;

    if ( b==NULL )
return( 0 );
    
    mb = 0;
    for ( i=0; i<b->move_cnt; ++i ) {
	FigureSegment *cs = &b->moves[i];
	if ( cs->cmd==c_if )
	    mb += blockCountMillibars(cs->ifblock);
	else if ( cs->cmd!=c_resetDance && cs->cmd!=c_end )
	    mb += cs->millibars;
    }
return( mb );
}
    
ValChange *valueChangeCopy(ValChange *input) {
    int i;
    ValChange *ret;

    if ( input==NULL )
return( NULL );
    for ( i=0; input[i].millibars!=-1; ++i )
	;
    if ( i==0 )
return( NULL );
    ret = calloc(i+1,sizeof(ValChange));
    for ( i=0; input[i].millibars!=-1; ++i )
	ret[i] = input[i];
    ret[i] = input[i];
return( ret );
}
    
RThetaChange *rThetaChangeCopy(RThetaChange *input) {
    int i;
    RThetaChange *ret;

    if ( input==NULL )
return( NULL );
    for ( i=0; input[i].millibars!=-1; ++i )
	;
    if ( i==0 )
return( NULL );
    ret = calloc(i+1,sizeof(RThetaChange));
    for ( i=0; input[i].millibars!=-1; ++i )
	ret[i] = input[i];
    ret[i] = input[i];
return( ret );
}

static MusicSegment *segmentCopy(MusicSegment *s) {
    MusicSegment *next;
    MusicSegment *first = NULL, *last = NULL, *cur;

    while ( s!=NULL ) {
	next = s->next;
	cur = malloc(sizeof(MusicSegment));
	memcpy(cur,s,sizeof(MusicSegment));
	if ( s->name!=NULL )
	    cur->name = strdup(s->name);
	if ( first==NULL )
	    first = cur;
	else
	    last->next = cur;
	last = cur;
	s = next;
    }
return( first );
}

void mdCopyContents(MusicDescription *to,MusicDescription *from) {
    mdFreeContents(to);
    memcpy(to,from,sizeof(MusicDescription));
    if ( from->filename!=NULL )
	to->filename = strdup(from->filename);

    to->intro = segmentCopy(from->intro);
    to->minorset = segmentCopy(from->minorset);
    to->lastminorset = segmentCopy(from->lastminorset);
    to->body = segmentCopy(from->body);
    to->ending = segmentCopy(from->ending);
}

char *danceLibDir(void) {
    /* !!!!! For now, this will do */
return( "/Users/georgewilliams/Projects/SquareDance/DanceLibs/" );
}

/* Make polynomials from control points */
void splineMake(FigureSegment *cs, Position *lastPoint) {
    double diff;

    if ( cs->cmd==c_lineto ) {
	cs->splines[0].d = lastPoint->x;
	cs->splines[0].c = cs->endPoint.x - lastPoint->x;
	cs->splines[1].d = lastPoint->y;
	cs->splines[1].c = cs->endPoint.y - lastPoint->y;

	cs->splines[0].b = cs->splines[0].a = cs->splines[1].b = cs->splines[1].a = 0;
    } else if ( cs->cmd==c_curveto ) {
	cs->splines[0].d = lastPoint->x;
	cs->splines[0].c = 3*(cs->cp1.x - lastPoint->x);
	cs->splines[0].b = 3*(cs->cp2.x - 2*cs->cp1.x + lastPoint->x);
	cs->splines[0].a = cs->endPoint.x - 3*cs->cp2.x + 3*cs->cp1.x - lastPoint->x;
	cs->splines[1].d = lastPoint->y;
	cs->splines[1].c = 3*(cs->cp1.y - lastPoint->y);
	cs->splines[1].b = 3*(cs->cp2.y - 2*cs->cp1.y + lastPoint->y);
	cs->splines[1].a = cs->endPoint.y - 3*cs->cp2.y + 3*cs->cp1.y - lastPoint->y ;

	diff = cs->splines[0].a+cs->splines[0].b+cs->splines[0].c+cs->splines[0].d - cs->endPoint.x;
	if ( diff>=1 || diff<=-1 )
	    assert( round(cs->splines[0].a+cs->splines[0].b+cs->splines[0].c+cs->splines[0].d) == round(cs->endPoint.x) );
	diff = cs->splines[1].a+cs->splines[1].b+cs->splines[1].c+cs->splines[1].d - cs->endPoint.y;
	if ( diff>=1 || diff<=-1 )
	    assert( round(cs->splines[1].a+cs->splines[1].b+cs->splines[1].c+cs->splines[1].d) == round(cs->endPoint.y) );
#ifndef NDEBUG
	{
	    double s1, s2;
	    s1 = atan2((3*cs->splines[1].a+2*cs->splines[1].b)+cs->splines[1].c,
		  (3*cs->splines[0].a+2*cs->splines[0].b)+cs->splines[0].c);
	    s2 = atan2(cs->endPoint.y-cs->cp2.y,cs->endPoint.x-cs->cp2.x);
	    s1 = round(10*s1); s2 = round(10*s2);
	    if (( s1==31 && s2==-31 ) || (s1==-31 && s2==31))	/* Remove the cut around PI */
		s1 = s2;
#if 0
 if ( s1!=s2 )
 printf( "%f,%f\n", 18/PI * s1, 18/PI * s2 );
	    assert(s1==s2);	/* This doesn't seem true for scililian dances and I'm not bothering to find out why */
#endif
	}
#endif
    }
}

/* Make control points from polynomials */
void splineCpsFromPoly(FigureSegment *cs) {
    if ( cs->cmd==c_lineto ) {
	/* I don't store the start point, and cp1,cp2 are unused for linear splines */
	cs->endPoint.x = cs->splines[0].d+cs->splines[0].c;
	cs->endPoint.y = cs->splines[1].d+cs->splines[1].c;
    } else if ( cs->cmd==c_curveto ) {
	cs->endPoint.x = cs->splines[0].d+cs->splines[0].c+cs->splines[0].b+cs->splines[0].a;
	cs->endPoint.y = cs->splines[1].d+cs->splines[1].c+cs->splines[1].b+cs->splines[1].a;

	cs->cp1.x = (cs->splines[0].c + 3*cs->splines[0].d)/3.0;
	cs->cp1.y = (cs->splines[1].c + 3*cs->splines[1].d)/3.0;

	cs->cp2.x = (cs->splines[0].b + 6*cs->cp1.x - 3*cs->splines[0].d)/3.0;
	cs->cp2.y = (cs->splines[1].b + 6*cs->cp1.y - 3*cs->splines[1].d)/3.0;
#if 0 /* ndef NDEBUG */
	{
/* rounding errors seem to be large, but the results seem approximately right */
	    double a;
	    a = cs->endPoint.x - 3*cs->cp2.x + 3*cs->cp1.x - cs->splines[0].d;
	    assert( a>cs->splines[0].a-.001 && a<cs->splines[0].a+.001);
	    a = cs->endPoint.y - 3*cs->cp2.y + 3*cs->cp1.y - cs->splines[1].d;
	    assert( a>cs->splines[1].a-.001 && a<cs->splines[1].a+.001);
	}
#endif
    }
}

static void splineBisect1D(Cubic *old,Cubic *before, Cubic *after,double t) {
    double s = 1.0-t;

    before->d = old->d;
    after->d  = old->d + t*(old->c + t*(old->b + t*old->a));

    before->c = t*old->c;
    after->c  = s*(old->c + t*(2*old->b + t*3*old->a));

    before->b = t*t*old->b;
    after->b  = s*s*(old->b + t*3*old->a);

    before->a = t*t*t*old->a;
    after->a  = s*s*s*old->a;
}

void splineBisect(FigureSegment *cs,FigureSegment split[2],double t) {
    /* t is between 0 and 1, not millibars */
    memset(split,0,2*sizeof(FigureSegment));
    split[1].endPoint =  cs->endPoint;
    split[0].splines[0].d = cs->splines[0].d;
    split[0].splines[1].d = cs->splines[1].d;
    split[0].cmd = split[1].cmd = cs->cmd;
    if ( cs->cmd == c_lineto ) {
	split[0].endPoint.x = split[1].splines[0].d =
	    cs->splines[0].d + t*cs->splines[0].c;
	split[0].endPoint.y = split[1].splines[1].d =
	    cs->splines[1].d + t*cs->splines[1].c;
	split[0].splines[0].c = split[0].endPoint.x - split[0].splines[0].d;
	split[0].splines[1].c = split[0].endPoint.y - split[0].splines[1].d;
	split[1].splines[0].c = split[1].endPoint.x - split[1].splines[0].d;
	split[1].splines[1].c = split[1].endPoint.y - split[1].splines[1].d;
    } else if ( cs->cmd==c_curveto ) {
	splineBisect1D(&cs->splines[0],&split[0].splines[0],&split[1].splines[0],t);
	splineBisect1D(&cs->splines[1],&split[0].splines[1],&split[1].splines[1],t);
	splineCpsFromPoly(&split[0]);
	splineCpsFromPoly(&split[1]);
    }	
}

/* Save those bits of state needed for establishing the video/audio timelines */
/*  so that after we do that we can run the dance normally */
Dance *danceSaveState(Dance *dance) {
    Dance *saved;
    int i;

    saved = malloc(sizeof(Dance));
    *saved = *dance;
    if ( dance->roles==NULL && dance->dancers!=NULL ) {
	/* Copy dancers, we're going to mess with them */
	saved->dancers = malloc(saved->people*sizeof(Person));
	memcpy(saved->dancers,dance->dancers,saved->people*sizeof(Person));
	for ( i=0; i<saved->people; ++i ) {
	    Block *temp;
	    temp = blockCopy(&saved->dancers[i].figures);
	    saved->dancers[i].figures = *temp;
	    free(temp);
	}
    }
return( saved );
}

void danceResetState(Dance *dance,Dance *starting_state) {
    int i;

    /* Do this even for figures. We made copies earlier */
    for ( i=0; i<dance->people; ++i ) {
	peopleFreeContents(&dance->dancers[i]);
    }
    free(dance->dancers);
    *dance = *starting_state;
    free(starting_state);
}

int danceIsCircle(Dance *dance) {

    if ( dance->formation==sp_circularMonople || dance->formation == sp_trioCircularMonople )
return( true );
    else if ( dance->formation>=sp_3CplCircle && dance->formation <= sp_7CplCircle )
return( true );
    else if ( dance->formation==sp_squaredSet || dance->formation==sp_square3 )
return( true );
    if ( strcmp(dance->name,"Bring Me Flowers")==0 ||
	    strcmp(dance->name,"Turning by Threes")==0 ||
	    strcmp(dance->name,"Wibsey Roundabout")==0 ||
	    strcmp(dance->name,"Winter Solstice")==0 ||
	    strcasecmp(dance->name,"I Care Not for These Ladies")==0 )
return( true );

return( false );
}

static int simpleEvalCondition(Condition *expr,int part) {

    switch ( expr->operator ) {
      case op_or:
	if ( simpleEvalCondition(expr->op1,part))
return( true );
	else
return( simpleEvalCondition(expr->op2,part));
      case op_and:
	if ( !simpleEvalCondition(expr->op1,part))
return( false );
	else
return( simpleEvalCondition(expr->op2,part));
      case op_not:
return( !simpleEvalCondition(expr->op1,part));

      case op_part:
return( part==expr->argument1 );

      case op_proper:
      case op_improper:
      case op_first:
      case op_last:
      case op_topset:
      case op_bottomset:
      case op_majorpos:
return( false );
    }
}

static Position *blockFindLastPosition(Block *b, int part) {
    int i;
    FigureSegment *cs;
    Position *end=NULL;

    i = b->move_cnt-1;
    while ( i>=0 ) {
	cs = &b->moves[i];
	if ( cs->cmd == c_lineto ||
	     cs->cmd == c_curveto ||
	     cs->cmd == c_moveto )
return( &cs->endPoint );

	if ( cs->cmd==c_if ) {
	    Block *ifb;
	    for ( ifb=cs->ifblock; ifb!=NULL; ifb=ifb->next ) {
		if ( ifb->condition==NULL || simpleEvalCondition(ifb->condition,part) )
		    end = blockFindLastPosition(ifb,part);
		    /* it is possible to have an if block where the banch we */
		    /*  want just contains a pause statement. So there is no */
		    /*  location info in the block */
		    /* if so, then look at the previous figuresegment */
		    if ( end!=NULL )
return( end );
		    else
	    break;
	    }
	}
	--i;
    }

return( NULL );
}

/* A simple comparison works fine in longways dances, but in circles there are */
/*  rounding errors from the rotations */
static int near( double x1, double x2) {
    const double Fudge = 1;
return( x1>x2-Fudge && x1<x2+Fudge );
}

static Person *danceProgressionFor(Dance *dance, Person *r, int part) {
    /* Find where r's last move puts him, if that matches another role's start */
    /*  position then return that role, otherwise return NULL */
    /* The part may or may not matter. Some of Gary Roodman's dances have */
    /*  two different permutations depending on the part */
    Position *end;
    int i;

    end = blockFindLastPosition(&r->figures,part);
    if ( end==NULL )
return( NULL );
    for ( i=0; i<dance->role_cnt; ++i ) {
	Person *p = &dance->roles[i];
	FigureSegment *cs = &p->figures.moves[0];

	if ( cs->cmd==c_moveto && near(cs->endPoint.x,end->x) && near(cs->endPoint.y,end->y) )
return( p );
    }
return( NULL );
}

struct setProgression *danceSetProgression(Dance *dance, struct setProgression *sp) {
    /* I don't deal fully with trios here, just couple dances */
    /* Nor do I deal with dances with more than 2 different progression parts */
    /* Nor with dances with choreographies */
    /* Nor with dances which rotate (Abernathy House alternates between a vertical longways set and a horizontal one) */
    /* Probably doesn't handle rounding errors in circle dances well */
    int i, part;
    char *mperm;
    char *fperm;

    memset(sp,0,sizeof(*sp));
    if ( dance->role_cnt>2*sizeof(sp->progression)) {
	fprintf( stderr, "Dance has too many roles to figure progression.\n" );
	sp->error = 1;
return( sp );
    } else if (( dance->formation >= sp_dupleMinor && dance->formation<=sp_tripleMinor ) ||
	    ( dance->formation >=sp_scottishDupleMinor && dance->formation<=sp_rotatedTripleMinor )) {
	fprintf( stderr, "Attempt to figure set progression on a minor set dance.\n" );
	sp->error = 1;
return( sp );
    } else if ( dance->comp_cnt>1) {
	fprintf( stderr, "Attempt to figure set progression on a dance with multiple choreographies.\n" );
	sp->error = 1;
return( sp );
    }
    if ( dance->comp_cnt==1 )
return( danceSetProgression(dance->components[0],sp));

    for ( part=1; part<=dance->part_cnt && part<=2; ++part ) {
	mperm = part==1 ? sp->progression : sp->progression2;
	fperm = part==1 ? sp->fprogression : sp->fprogression2;
	for ( i=0; i<dance->role_cnt; ++i ) {
	    Person *r = &dance->roles[i];
	    Person *end = danceProgressionFor(dance, r, part);

	    if ( end==NULL ) {
		sp->error = 1;
return( sp );
	    }
	    if ( end->sex!=r->sex )
		sp->impropriety = true;
	    if ( r->sex==s_male || r->sex==s_center )
		mperm[(int) (end->effective_role)] = (int) r->effective_role;
	    else if ( r->sex==s_female || r->sex==s_right )
		fperm[(int) (end->effective_role)] = (int) r->effective_role;
	}
    }
    for ( i=0; i<dance->role_cnt; ++i ) {
	if ( sp->progression[i]!=sp->fprogression[i] ) {
	    sp->women_differ = 1;
    break;
	}
	if ( sp->progression2[i]!=sp->fprogression2[i] ) {
	    sp->women_differ = 1;
    break;
	}
    }
    if ( dance->part_cnt>1 ) {
	for ( i=0; i<dance->role_cnt; ++i ) {
	    if ( sp->progression[i]!=sp->progression2[i] ) {
		sp->part2_differs = 1;
	break;
	    }
	}
    }
    for ( i=0; i<dance->role_cnt/2; ++i ) {
	if ( sp->progression[i]!=i )
    break;
    }
    if ( i==dance->role_cnt/2 && !sp->women_differ && !sp->part2_differs )
	sp->identity = 1;
    else if ( i!=dance->role_cnt/2 ) {
	/* in 3,4,5,etc. couple dances either everyone ends where they started */
	/*  or everyone moves to a new place. It's generally an error when one */
	/*  dancer is always in the same role but others are not */
	int j,k;
	for ( j=0; j<dance->role_cnt/2; ++j ) {
	    if ( sp->progression[j]==j ) {
		fprintf( stderr, "%s: Bad progression pattern, couple %d stays in one place while couple %d take new roles.\n",
			dance->name, j+1, i+1 );
		fprintf( stderr, " (%d", sp->progression[0]+1);
		for ( k=1; k<dance->role_cnt/2; ++k )
		    fprintf( stderr, ", %d", sp->progression[k]+1 );
		fprintf( stderr,")\n" );
	break;
	    }
	}
    }
    sp->couple_cnt = dance->role_cnt/2;
return( sp );
}

int danceHasActualBars(Dance *dance) {
    int i;

    for ( i=0; i<dance->call_cnt; ++i ) {
	CallList *ci = &dance->calls[i];
	if ( ci->actual_millibars!=0 && ci->actual_millibars!=ci->millibars )
return( true );
    }
return( false );
}

int danceHasProgression(Dance *dance) {
    /* Is the ending position the same as the starting position? */
    /* Note in a triple minor the 3s will usually not move, the other two couples must */
    int i;

    for ( i=0; i<dance->role_cnt; ++i ) {
	Person *p = &dance->roles[i];
	Position *end = blockFindLastPosition(&p->figures,1);
	Position *start = &p->figures.moves[0].endPoint;
	if ( end==NULL )
	    /* Found no movement, so it stayed in the same spot */;
	else if ( start->x!=end->x || start->y!=end->y )
return( true );
    }
return( false );
}

int danceNeedsImproperCrossover(Dance *dance) {
    int i;
    int man_pos=-1, woman_pos=-1;

    if ( dance->style!=ds_scottish )
return( false );

    if ( dance->becket_style!=bs_none )
return( true );

    if ( dance->improper_mask==0 )
return( false );
    if ( dance->formation==sp_threeFaceThree )
return( false );
/* Round the room dances (sicillian) don't get improper cross-overs */
    if ( dance->formation==sp_dupleMinor || dance->formation==sp_fourFaceFour )
return( false );

    for ( i=0; i<dance->role_cnt; ++i ) {
	Person *p = &dance->roles[i];
	if ( p->effective_role>=r_outAtTop )
    continue;
	if ( (dance->improper_mask&(1<<(i/2)))==0 ) {
	    /* proper */
	    if ( p->sex== s_male ) {
		man_pos=p->figures.moves[0].endPoint.y;
	    } else {
		woman_pos=p->figures.moves[0].endPoint.y;
		if ( man_pos!=-1 )
    break;
	    }
	}
    }
    if ( woman_pos==-1 || man_pos==-1 )
return( false );		/* Nobody is proper? */

    for ( i=0; i<dance->role_cnt; ++i ) {
	Person *p = &dance->roles[i];
	if ( p->effective_role>=r_outAtTop )
    continue;
	/* improper dancers, man in woman's line, woman in man's */
	if ( (dance->improper_mask&(1<<(i/2)))!=0 ) {
	    if (( p->sex== s_male && p->figures.moves[0].endPoint.y!=woman_pos ) ||
		    ( p->sex== s_female && p->figures.moves[0].endPoint.y!=man_pos ))
return( true );
	}
    }
    /* All improper people in the right place */
return( false );
}

void danceForceImproper(Dance *dance) {
    int i;
    int man_pos=-1, woman_pos=-1;
    float man_face, woman_face;

    if ( dance->improper_mask==0 )
return;
    if ( dance->formation==sp_threeFaceThree )
return;

    for ( i=0; i<dance->people; ++i ) {
	Person *p = &dance->dancers[i];
	if ( (dance->improper_mask&(1<<(i/2)))==0 ) {
	    /* proper */
	    if ( p->sex== s_male ) {
		man_pos=p->figures.moves[0].endPoint.y;
		man_face = p->figures.moves[0].degrees;
	    } else {
		woman_pos=p->figures.moves[0].endPoint.y;
		woman_face = p->figures.moves[0].degrees;
		if ( man_pos!=-1 )
    break;
	    }
	}
    }
    if ( woman_pos==-1 || man_pos==-1 )
return;		/* Nobody is proper? */

    for ( i=0; i<dance->people; ++i ) {
	Person *p = &dance->dancers[i];
	if ( (dance->improper_mask&(1<<(i/2)))!=0 ) {
	    if ( p->sex==s_male ) {
		p->now_orientation = woman_face;
		p->now.y = woman_pos;
		p->figures.moves[0].degrees = woman_face;
		p->figures.moves[0].endPoint.y = woman_pos;
	    } else {
		p->now_orientation = man_face;
		p->now.y = man_pos;
		p->figures.moves[0].degrees = man_face;
		p->figures.moves[0].endPoint.y = man_pos;
	    }
	}
    }
}

static char *PDLicenseNames[] = {
/* Charles Bolton has donated his dances and his adaptations to CDSS and */
/*  posted them online with a creative commons license */
	"Charles Bolton",
/* Ken Sheffield has did likewise with his adaptations to CDSS and */
/*  posted them online with a creative commons license */
	"Ken Sheffield",
/* Hillgrove (The Scollar's Companion) is out of copyright and needs no adaptation */
	"Thomas Hillgrove",
/* De Garmo (The Prompter) is out of copyright and needs no adaptation */
	"William B. De Garmo",
/* Elias Howe (Complete Ball-Room Hand Book) is out of copyright and needs no adaptation */
	"Elias Howe",
/* Scott Pfitzinger's Playford site is licensed under a creative commons attibution non-comercial licensed */
	"Scott Pfitzinger",

/* And, of course, my stuff is free */
	"George Williams",
	NULL };

static char *PDNames[] = {
/* Hillgrove (The Scollar's Companion) is out of copyright and needs no adaptation */
	"Thomas Hillgrove",
/* De Garmo (The Prompter) is out of copyright and needs no adaptation */
	"William B. De Garmo",
/* Elias Howe (Complete Ball-Room Hand Book) is out of copyright and needs no adaptation */
	"Elias Howe",

	NULL };


int danceHasPublicDomainLicense( Dance *dance ) {
    int i;

    for ( i=0; PDLicenseNames[i]!=NULL; ++i ) {
	int nlen = strlen(PDLicenseNames[i]);

	if ( dance->author!=NULL && strncasecmp(dance->author,PDLicenseNames[i],nlen)==0 && dance->interpreter==NULL)
return( true );
	if ( dance->interpreter!=NULL && strncasecmp(dance->interpreter,PDLicenseNames[i],nlen)==0 )
return( true );
    }


    /* The transcriptions of Playford dances on the playforddance site are licensed */
    /*  under a creative commons license */
    if ( dance->source_url!=NULL && strncasecmp(dance->source_url,"http://playforddances.com/dances",strlen("http://playforddances.com/dances"))==0 )
return( true );

    /* Note: the Ottawa site claims to release stuff under CC-A-NC-SA but I */
    /*  don't believe they have permission to do that. So better not to pass */
    /*  their stuff along */

    if ( dance->license!=NULL )
return( true );

return( false );
}

int danceIsPublicDomain( Dance *dance ) {
    int i;
    time_t now;
    struct tm *tm;
    int this_year;

    /* US public domain rules: 95 years after publication year */
    /* UK public domain rules: 70 years after death of author */

    time(&now);
    tm = localtime(&now);
    this_year = tm->tm_year+1900;
    if ( dance->year_interpreted>0 && dance->year_interpreted<this_year-95 )
return( true );
    else if ( dance->year_interpreted<0 && -dance->year_interpreted<this_year-105 )
return( true );
    if ( dance->interpreter==NULL ) {
	if ( dance->year_written>0 && dance->year_written<this_year-95 )
return( true );
	else if ( dance->year_written<0 && -dance->year_written<this_year-105 )
return( true );
    }

    for ( i=0; PDNames[i]!=NULL; ++i ) {
	int nlen = strlen(PDNames[i]);

	if ( dance->author!=NULL && strncasecmp(dance->author,PDNames[i],nlen)==0 && dance->interpreter==NULL)
return( true );
	if ( dance->interpreter!=NULL && strncasecmp(dance->interpreter,PDNames[i],nlen)==0 )
return( true );
    }

return( false );
}

char *danceLicenseText( Dance *dance, char *buffer, int blen ) {

    if ( dance->license!=NULL ) {
	snprintf( buffer, blen, "%s", dance->license );
return( buffer );
    }

    if (( dance->author!=NULL && strncasecmp(dance->author,"Charles Bolton",strlen("Charles Bolton"))==0 ) ||
	    ( dance->interpreter!=NULL && strncasecmp(dance->interpreter,"Charles Bolton",strlen("Charles Bolton"))==0 )) {
	if ( dance->interpreter!=NULL )
	    snprintf( buffer, blen, "The dances of Charles Bolton (including interpretations like this one) are licenced under a <a href=\"https://creativecommons.org/licenses/\">Creative Commons</a> Attribution-NonCommercial-ShareAlike: CC BY-NC-SA license by the <a href=\"https://www.cdss.org/programs/cdss-news-publications/cdss-online-library/bolton-collection/bolton-collection-retreads\">CDSS Online Library</a>." );
	else
	    snprintf( buffer, blen, "The dances of Charles Bolton (including this one) are licenced under a <a href=\"https://creativecommons.org/licenses/\">Creative Commons</a> Attribution-NonCommercial-ShareAlike: CC BY-NC-SA license by the <a href=\"https://www.cdss.org/programs/cdss-news-publications/cdss-online-library/bolton-collection/bolton-collection-retreads\">CDSS Online Library</a>." );
return( buffer );
    }

    if (( dance->author!=NULL && strncasecmp(dance->author,"Ken Sheffield",strlen("Ken Sheffield"))==0 ) ||
	    ( dance->interpreter!=NULL && strncasecmp(dance->interpreter,"Ken Sheffield",strlen("Ken Sheffield"))==0 )) {
	if ( dance->interpreter!=NULL )
	    snprintf( buffer, blen, "The dances of Ken Sheffield (including adaptations like this one) are licenced under a <a href=\"https://creativecommons.org/licenses/\">Creative Commons</a> Attribution-NonCommercial-ShareAlike: CC BY-NC-SA license by the <a href=\"https://www.cdss.org/programs/cdss-news-publications/cdss-online-library/bolton-collection/bolton-collection-retreads\">CDSS Online Library</a>." );
	else
	    snprintf( buffer, blen, "The dances of Ken Sheffield (including this one) are licenced under a <a href=\"https://creativecommons.org/licenses/\">Creative Commons</a> Attribution-NonCommercial-ShareAlike: CC BY-NC-SA license by the <a href=\"https://www.cdss.org/programs/cdss-news-publications/cdss-online-library/sheffield\">CDSS Online Library</a>." );
return( buffer );
    }

    if (( dance->author!=NULL && strncasecmp(dance->author,"George Williams",strlen("George Williams"))==0 ) ||
	    ( dance->interpreter!=NULL && strncasecmp(dance->interpreter,"George Williams",strlen("George Williams"))==0 )) {
	if ( dance->interpreter!=NULL )
	    snprintf( buffer, blen, "The dances of George Williams (including interpretations like this one) are licenced under a <a href=\"https://creativecommons.org/licenses/\">Creative Commons</a> Attribution-NonCommercial-ShareAlike: CC BY-NC-SA license." );
	else
	    snprintf( buffer, blen, "The dances of George Williams (including this one) are licenced under a <a href=\"https://creativecommons.org/licenses/\">Creative Commons</a> Attribution-NonCommercial-ShareAlike: CC BY-NC-SA license." );
return( buffer );
    }

    if (( dance->source_url!=NULL && strncasecmp(dance->source_url,"http://playforddances.com/dances",strlen("http://playforddances.com/dances"))==0 ) ||
	    ( dance->author!=NULL && strncasecmp(dance->author,"Scott Pfitzinger",strlen("Scott Pfitzinger"))==0 )) {
	snprintf( buffer, blen, "The transcriptions and interpretations of <a href=\"http://playforddances.com/\">Playford dances</a> found on http://playforddances.com/ from the various editions of <em>The Dancing Master</em> are licenced under a <a href=\"https://creativecommons.org/licenses/\">Creative Commons</a> Attribution-NonCommercial-ShareAlike: CC BY-NC-SA license." );
return( buffer );
    }

return( NULL );
}

    /* Following statements true in 2018. There's a 95 year cut-off so it's 1924 in 2019 */
    /* Anything published before 1923 is public domain */
    /* Things published between 1923 and 1963 have a 28 year copyright which might be renewed for a max of 95 years */
    /*  (things published between 1923 and 1963 without copyright notice are public domain) */
    /* Things published with notice 1964-1977 get an automatic 95 year copyright */
    /* Things created but not published before 1978, notice is irrelevant, protection lasts for the life of author+70 years */
    /* Works created after 1978, copyright protection lasts for life of author+70 years for individuals, corporate or anonymous works are protected for 95 years from publication or 120 years from creation which ever is shorter */
int danceInCopyright( Dance *dance, int curYear ) {

    if ( danceHasPublicDomainLicense(dance))
return( false );

    if ( dance->year_written>curYear-95 || dance->year_written<-curYear+95 )
return( true );
    if ( dance->year_interpreted>curYear-95 || dance->year_interpreted<-curYear+95 )
return( true );
    if ( dance->year_written==0 )
return( true );		/* Don't know, let's be cautious */
    if ( dance->year_interpreted==0 )
return( true );		/* Don't know, let's be cautious */
    /* In cases where there is no adapter (or I am the adapter) I will set */
    /*  year_interpreted to year_written and leave interpreter blank */

return( false );
}

    /* The following people were not the devisors of the dances they published*/
static char *collectors[] = {
    "Johnson",
    "Johnſon",
    "Dale",
    "Boag"
    "Rutherford",
    "Walsh",
    "Walſh",
    "Thompſon",
    "Thompson",
    "English Traditional",
    "Cecil Sharp & Maud Karpeles",
    "Cecil Sharp",
    "Lovelace Manuſcript",
    "Preston",
    "Preſton",
    "Cahuſac",
    "Cahusac",
    "W.M. Cahuſac",
    "W.M. Cahuſac's Annual Collection",
    "Thompſon's Compleat Collection",
    "Traditional",
    "Wright",
    "Voight",
    "Henry Biſhop",
    "Dance fan",
    "Thomas Hillgrove",
    "Univerſal Magazine",
    "Merry Lad",
    "Richard Bride",
    "Peter Thompſon",
    "Goulding",
    "Unknown",
    "Gentlemen's Magazine",
    "G. Willſim",
    "Campbell",
    "Pierre Trappeniers",
    "Feuillet",
    "Saltator",
    "Elias Howe",
    "Essex Manuscript",
    "Richmond",
    "John Griffiths",
    "John Duval",
    "Anonymous",
    "Castle Menzies",
    "Los Angeles RSCDS 50th Aniversary",
    "Golden Jubilee Dances",
    "Anon. (Castle Menzies)",
    "Preston",
    "Longman & Broderip",
    "Longman",
/*    "William Campbell", */		/* Nope, he actually devised some of his dances */
    "Walter F. Gillies",
    "J. P. Boulogne",
    "Goulding & D'Almaine",
    "Skillern & Challoner",
    "Smyth",
    "W. Smyth",
    "John Rutherford",
    "David Rutherford",
    "Rutherford",
    "Bride",
    "Thomas Skillern",
    "Skillern",
    "Straight & Skillern",
    "W. Boag",
    "Button & Whitaker",
    "George Appleton",
    "Appleton",
    "Robert Bremner",
    NULL
};

int danceAuthorNotDevisor(Dance *dance) {
    int i;

    if ( dance->author==NULL )
return( true );
    if ( strstr(dance->author,"Playford")!=NULL )
return( true );
    if ( strstr(dance->author,"RSCDS")!=NULL )
return( true );
    for ( i=0; collectors[i]!=NULL; ++i ) {
	if ( strcasecmp(collectors[i],dance->author)==0 )
return( true );
    }
return( false );
}
    
int danceHasTag(Dance *dance, char *tagname) {
    char *pt, *tags, *end;
    if ( dance->tags==NULL )
return( false );
    tags = dance->tags;
    pt = strcasestr(tags,tagname);
    if ( pt==NULL )
return( false );

    end = pt+strlen(tagname);
    while ( *end==' ' ) ++end;
    if ( *end!=',' && *end!='\0' )
return( false );
    while ( pt>tags && pt[-1]==' ' )
	--pt;
    if ( pt==tags )
return( true );
    else if ( pt[-1]==',' )
return( true );

return( false );
}

char *romanNumeral(char *buf, int buflen, int val, int useLC) {
    static struct letters { int val; char *one; char *five; }
	uc[] = {{ 1000000, "\u0304M", NULL },
		{  100000, "\u0304C", "\u0304D" },
		{   10000, "\u0304X", "\u0304L" },
		{    1000, "\u0304I", "\u0304V" },
		{     100, "C", "D" },
		{      10, "X", "L" },
		{       1, "I", "V" },
		{	0, "N" }},
	lc[] = {{ 1000000, "\u0304M", NULL },
		{  100000, "\u0304C", "\u0304d" },
		{   10000, "\u0304x", "\u0304l" },
		{    1000, "\u0304i", "\u0304v" },
		{     100, "c", "d" },
		{      10, "x", "l" },
		{       1, "i", "v" },
		{	0, "n" }},
	*numerals;
    char *pt = buf;
    int i,j, usedBig;
    int v = val;
    /* Random info: S means 1/2. A dot means 1/12, three dots in a therefore shape means 3/12 or 1/4 */

    if ( val>3999999 ) {
	*pt++ = '\xE2';		/* Infinity */
	*pt++ = '\x88';
	*pt++ = '\x9E';
    } else if ( val<0 ) {
	*pt++ = 'N';
	*pt++ = 'a';
	*pt++ = 'N';
    } else if ( val==0 ) {
	if ( useLC ) {
	    *pt++ = 'n';
	    *pt++ = 'u';
	    *pt++ = 'l';
	    *pt++ = 'l';
	    *pt++ = 'a';
	} else {
	    *pt++ = 'N';
	    *pt++ = 'U';
	    *pt++ = 'L';
	    *pt++ = 'L';
	    *pt++ = 'A';
	}
    } else {
	numerals = useLC ? lc : uc;
	i = 0;
	usedBig = val>3999;

	while ( val>0 ) {
	    if ( val>=numerals[i].val ) {
		int digit = val/numerals[i].val;

		if ( !usedBig && val<3999 && val>=1000 ) {
		    /* we have two ways of writing 1000 M and Imacron */
		    /* use IMacron if we have numbers which need digits above 1000 */
		    /* use M if the number doesn't need any larger digits */
		    for ( j=0; j<digit; ++j )
			*pt++ = 'M';
		} else {
		    if ( digit==0 ) {
			/* Do Nothing */;
		    } else if ( digit<4 ) {
			for ( j=0; j<digit; ++j ) {
			    strcpy(pt,numerals[i].one);
			    pt+=strlen(pt);
			}
		    } else if ( digit==4 ) {
			strcpy(pt,numerals[i].one);
			pt+=strlen(pt);
			strcpy(pt,numerals[i].five);
			pt+=strlen(pt);
		    } else if ( digit==9 ) {
			strcpy(pt,numerals[i].one);
			pt+=strlen(pt);
			if ( !usedBig && numerals[i].val==100 )
			    *pt++ = 'M';
			else {
			    strcpy(pt,numerals[i-1].one);
			    pt+=strlen(pt);
			}
		    } else {
			strcpy(pt,numerals[i].five);
			pt+=strlen(pt);
			for ( j=5; j<digit; ++j ) {
			    strcpy(pt,numerals[i].one);
			    pt+=strlen(pt);
			}
		    }
		}
		val -= digit * numerals[i].val;
	    }
	    ++i;
	}
    }
    *pt='\0';
    if ( pt>buf+buflen ) {
	fprintf( stderr, "Buffer too small in roman numeral for %d, %s\n", v, buf );
	assert( pt<=buf+buflen );
    }
return( buf );
}
    
char *partAndMusic(char *buf,int blen, CallList *call) {
    char *bpt;
    int i;

    if ( call->part_mask==(unsigned int) -1 || call->part_mask==0 )
return( call->musicName );
    if ( strchr(call->musicName,'.')!=NULL ) {
	fprintf( stderr, "Call with a partmask has a musicName with a part identifier: %s %s\n", call->musicName, call->callName );
return( call->musicName );
    }
    if ( call->part_mask&(NOTLAST_MASK|NOTFIRST_MASK) )
return( call->musicName );

    bpt = buf;
    for ( i=0; i<28; ++i ) if ( (1<<i) & call->part_mask ) {
	if ( bpt!=buf )
	    *bpt++ = ',';
	romanNumeral(bpt,blen-(bpt-buf), i+1,false);
	bpt += strlen(bpt);
#if 0
	v = i+1;
	while ( v>10 ) {
	    *bpt++ = 'X';
	    v-=10;
	}
	if ( v==10 ) {
	    *bpt++ = 'X';
	} else if ( v==9 ) {
	    *bpt++ = 'I';
	    *bpt++ = 'X';
	} else if ( v==4 ) {
	    *bpt++ = 'I';
	    *bpt++ = 'V';
	} else {
	    if ( v>=5 ) {
		*bpt++ = 'V';
		v-=5;
	    }
	    while ( v>0 ) {
		*bpt++ = 'I';
		--v;
	    }
	}
#endif
    }
    if ( call->part_mask & LAST_MASK ) {
	if ( bpt!=buf )
	    *bpt++ = ',';
	strcpy(bpt,"(Finale)");
	bpt += strlen(bpt);
    }
    if ( call->part_mask & FIRST_MASK ) {
	if ( bpt!=buf )
	    *bpt++ = ',';
	strcpy(bpt,"(Intro)");
	bpt += strlen(bpt);
    }
    if ( bpt!=buf && call->musicName[0]!='\0' ) {
	*bpt++ = '.';
	strcpy(bpt,call->musicName);
	bpt += strlen(call->musicName );
    }
    *bpt++ = '\0';
    if ( bpt>buf+blen ) {
	fprintf( stderr, "Buffer too small in partAndMusic. %s\n", buf );
	abort();
    }
return( buf );
}

static int isvowel(char ch) {

return( ch=='E' || ch=='e' || ch=='A' || ch=='a' || ch=='I' || ch=='i' || ch=='o' || ch=='O' || ch=='O' || ch=='U' || ch=='u' || ch=='Y' || ch=='y' );
}

char *strip(char *name) {

    /* Make " 'Twas" match "Twas" */
    while ( !isalnum(*name) && *(unsigned char *)name!=0xc3 && *name!='\0' )
	++name;

    /* Don't want to match "A la Mode de France" */
    if ( name[0]=='A' && name[1]==' ' && !(name[2]=='l' && name[3]=='a' && name[4]==' ' && name[5]=='M'))
return( name+2 );
    if ( name[0]=='L' && name[1]=='\'' && isvowel(name[2]) )
return( name+2 );

    if ( name[0]=='A' && name[1]=='n' && name[2]==' ' )
return( name+3 );
    if ( name[0]=='L' && name[1]=='a' && name[2]==' ' )
return( name+3 );
    if ( name[0]=='L' && name[1]=='e' && name[2]==' ' )
return( name+3 );

    if ( name[0]=='T' && name[1]=='h' && name[2]=='e' && name[3]==' ' )
return( name+4 );
    if ( name[0]=='L' && name[1]=='e' && name[2]=='s' && name[3]==' ' )
return( name+4 );
    if ( name[0]=='L' && name[1]=='o' && name[2]=='s' && name[3]==' ' )
return( name+4 );
    if ( name[0]=='L' && name[1]=='a' && name[2]=='s' && name[3]==' ' )
return( name+4 );

#if 0	/* Used to strip "return to" (for "return to lasbridge lane") but that doesn't work for "return to newcastle", done better with sort-name now */
    if ( name[0]=='R' && name[1]=='e' && name[2]=='t' && name[3]=='u' &&
	    name[4]=='r' && name[5]=='n' && name[6]==' ' && name[7]=='t' && 
	    name[8]=='o' && name[9]==' ' )
return( name+10 );
#endif

return( name );
}

char *removeLongs(char *buffer, int blen, const char *_name,int doquotes) {
    char *bpt, *bend = buffer+blen;
    const unsigned char *name = (const unsigned char *) _name;
    int ch;
    /* also remove yogh */

    for ( bpt=buffer; (ch = *name++); ) {
	if ( doquotes && (ch=='\\' || ch=='"' || ch=='\'' )) {
	    *bpt++ = '\\';
	    *bpt++ = ch;
	} else if ( ch==0xc5 && name[0]==0xbf ) {	/* Long s */
	    *bpt++ = 's';
	    ++name;
	} else if ( ch==0xc3 && name[0]==0x9f ) {	/* es zet */
	    *bpt++ = 's';
	    *bpt++ = 's';
	    ++name;
	} else if ( ch==0xc8 && name[0]==0x9c ) {	/* Yogh */
	    *bpt++ = 'Z';
	    ++name;
	} else if ( ch==0xc8 && name[0]==0x9d ) {	/* yogh */
	    *bpt++ = 'z';
	    ++name;
	} else
	    *bpt++ = ch;
	if ( bpt>=bend ) {
	    fprintf( stderr, "Buffer too small in removeLongs.\n" );
	    abort();
	}
    }
    *bpt = '\0';
return( buffer );
}

char *makeSortName(char *buffer,int size,char *name,char *sortName) {
    char space[500];
    char *pt, *pt2, *end, *saint=NULL;
    int val;

    if ( sortName!=NULL )
return( sortName );

    removeLongs(space,sizeof(space),name, true);

    /* Remove articles */
    pt = strip(space);

    /* Change St. to Saint */
    saint="";
    if ( strncmp(pt,"St. ",4)==0 ) {
	pt += 4;
	saint = "Saint ";
    }

    /* Look of initial accents */
    if ( *(unsigned char *)pt== 0xc3 ) {
	if ( ((unsigned char *)pt)[1]>=0x80 && ((unsigned char *)pt)[1]<=0x85 ) {
	    ++pt; pt[0]='A';
	} else if ( ((unsigned char *)pt)[1]==0x86 ) {
	    pt[0]='A';
	    pt[1]='E';
	} else if ( ((unsigned char *)pt)[1]==0x87 ) {
	    ++pt; pt[0]='C';
	} else if ( ((unsigned char *)pt)[1]>=0x88 && ((unsigned char *)pt)[1]<=0x8b ) {
	    ++pt; pt[0]='E';
	} else if ( ((unsigned char *)pt)[1]>=0x8c && ((unsigned char *)pt)[1]<=0x8f ) {
	    ++pt; pt[0]='I';
	} else if ( ((unsigned char *)pt)[1]==0x90 ) {
	    pt[0]='T';
	    pt[1]='h';
	} else if ( ((unsigned char *)pt)[1]==0x91 ) {
	    ++pt; pt[0]='N';
	} else if ( ((unsigned char *)pt)[1]>=0x92 && ((unsigned char *)pt)[1]<=0x98 ) {
	    ++pt; pt[0]='O';
	} else if ( ((unsigned char *)pt)[1]>=0x99 && ((unsigned char *)pt)[1]<=0x9C ) {
	    ++pt; pt[0]='U';
	}
    }

    /* look for leading numbers */
    val = strtol(pt,&end,10);
    pt2 = strchr(pt,'\n');
    if ( pt2!=NULL )
	*pt2='\0';

    /* Remove variants */
    pt2 = strchr(pt,'~');
    if ( pt2!=NULL ) {
	if ( pt2>pt && pt2[-1]==' ' )
	    --pt2;
	*pt2 = '\0';
    }

    if ( pt==end )
	snprintf( buffer, size, "%s%s", saint, pt );
    else
	snprintf( buffer, size, "%s%04d%s", saint, val, end );
return( buffer );
}

char *makeMusicDescriptionID(char *buffer,int size,char *filename) {
    char *pt, *end, *bend;

    pt = strstr(filename,"MD/");
    if ( pt!=NULL )
	filename=pt+3;
    end = strrchr(filename,'.');
    pt = buffer;
    bend = buffer+size-1;
    while ( filename!=end && *filename!='\0' && pt<bend ) {
	if ( *filename=='/' ) {
	    ++filename;
    continue;
	}
	*pt++ = *filename++;
    }
    *pt = '\0';
return( buffer );
}
    

char *addEntities(char *buffer, int blen, const char *name, int do_lt) {
    char *bpt, *bend = buffer+blen;
    int ch;

    for ( bpt=buffer; (ch = *name++); ) {
	if ( ch=='\\' || ch=='"' || ch=='\'' ) {
	    *bpt++ = '\\';
	    *bpt++ = ch;
	} else if ( ch=='&' ) {
	    *bpt++ = '&';
	    *bpt++ = 'a';
	    *bpt++ = 'm';
	    *bpt++ = 'p';
	    *bpt++ = ';';
	} else if ( ch=='<' && do_lt ) {
	    *bpt++ = '&';
	    *bpt++ = 'l';
	    *bpt++ = 't';
	    *bpt++ = ';';
	} else if ( ch=='\n' ) {
	    *bpt++ = ' ';
	} else
	    *bpt++ = ch;
	if ( bpt>=bend ) {
	    fprintf( stderr, "Buffer too small in addEntities.\n" );
	    abort();
	}
    }
    *bpt = '\0';
return( buffer );
}

void dumpCalls(FILE *output,Dance *dance,char *buffer,int sizebuf) {
    int k, bars;
    char name1[500];
    char *musicName="";

    fprintf( output, "<table style=\\\"text-align: left;\\\"> " );
    if ( dance->call_cnt!=0 ) {
	bars=1;
	if ( dance->calls[0].musicName[0]=='\0' ) {
	    for ( k=0; k<dance->call_cnt; ++k ) {
		if ( dance->calls[k].actual_millibars!=0 )
		    fprintf( output, "<tr><td>%d-%d</td><td>%s</td></tr> ",
			    bars, bars+dance->calls[k].actual_millibars/1000-1,
			    addEntities(buffer,sizebuf,dance->calls[k].callName,false) );
		bars += dance->calls[k].actual_millibars/1000;
	    }
	} else {
	    for ( k=0; k<dance->call_cnt; ++k ) {
		if ( dance->calls[k].musicName[0]!='\0' )
		    bars=1;
		if ( dance->calls[k].musicName[0]!='\0' )
		    musicName = partAndMusic(name1,sizeof(name1), &dance->calls[k]);
		if ( dance->calls[k].actual_millibars!=0 ) {
		    fprintf( output, "  <tr><td>%s</td><td>%d-%d</td><td>%s</td></tr>",
			    musicName,
			    bars, bars+dance->calls[k].actual_millibars/1000-1,
			    addEntities(buffer,sizebuf,dance->calls[k].callName,false) );
		    musicName="";
		}
		bars += dance->calls[k].actual_millibars/1000;
	    }
	}
    } else if ( dance->choreo_cnt!=0 && dance->sequence[0].figure->call_cnt!=0 ) {
	int i, j;

	for ( i=0; i<dance->choreo_cnt; ++i )
	    dance->sequence[i].figure->ticked = false;
	for ( i=0; i<dance->choreo_cnt; ) {
	    Dance *figure = dance->sequence[i].figure;
	    for ( j=i+1; j<dance->choreo_cnt && figure==dance->sequence[j].figure && dance->sequence[i].active==dance->sequence[j].active; ++j );
	    fprintf( output, "  <tr><td colspan='2'><em>%s</em>",  figure->name );
	    if ( dance->sequence[0].active!=0 )
		fprintf( output, " with %ds active", dance->sequence[0].active );
	    if ( j!=i+1 )
		fprintf( output, " repeats %d times", j-i );
	    i=j;
	    fprintf( output, "</td></tr>\n" );
	    if ( ! figure->ticked ) {
		fprintf( output, "  <tr><td>&nbsp;</td><td>" );
		dumpCalls(output,figure,buffer,sizebuf);
		fprintf( output, "</td></tr>\n" );
		figure->ticked = true;
	    }
	}
    }
    fprintf( output, "</table>" );
}

char *danceMusicType(Dance *dance, int shortForm) {
    int i;
    char **musicNames = shortForm ? musicTypesShort : musicTypes;

    if ( dance==NULL )
return( NULL );
    if ( dance->tags==NULL )
return( NULL );
    if ( danceHasTag(dance,"medley") )
return( shortForm ? "M" : "medley" );
    for ( i=0; musicTypes[i]!=NULL; ++i ) {
	if ( danceHasTag(dance,musicTypes[i]) )
return( musicNames[i] );
    }
return( NULL );
}

char *danceScottishDanceType(Dance *dance) {
    char *formation = NULL;

    switch ( dance->formation ) {
      case sp_scottishTripleMinor:
	formation = "3C/4C";
      break;
      case sp_scottishDupleMinor:
	formation = "2C/4C";
      break;
/* The next three are for round the room dances (sicilian) */
      case sp_dupleMinor:
	formation = "2C/2C";
      break;
      case sp_threeFaceThree:
	formation = "2T/2T";
      break;
      case sp_fourFaceFour:
	formation = "4C/4C";
      break;
      case sp_facingCouples:	/* Foursome Reel */
	formation = "2C";
      break;
      case sp_3Couple:
	formation = "3C";
      break;
      case sp_4Couple:
	formation = "4C";
      break;
      case sp_5Couple:
	formation = "5C";
      break;
      case sp_6Couple:
	formation = "6C";
      break;
      case sp_3CplCircle:
	formation = "Tri";
      break;
      case sp_squaredSet:
	formation = "Square";
      break;
      default:
	formation = NULL;
      break;
    }
    if ( danceHasTag(dance,"sicilian")) {
	switch ( dance->formation ) {
	  case sp_dupleMinor:
	    formation = "2C/2C Round the Room";
	  break;
	  case sp_threeFaceThree:
	    formation = "2T/2T Round the Room";
	  break;
	  case sp_fourFaceFour:
	    formation = "4C/4C Round the Room";
	  break;
	  default:
	    formation = "Round the Room";
	  break;
	}
    } else if ( formation!=NULL && dance->improper_mask!=0 ) {
	static char space[100];
	int i, len;

	strcpy(space,formation);
	len = strlen( space );
	space[len++] = ' ';
	for ( i=0; i<8; ++i ) {
	    if ( dance->improper_mask & (1<<i)) {
		space[len++] = '0'+i+1;
		space[len++] = 's';
		space[len++] = ',';
	    }
	}
	space[len-1] = ' ';
	strcpy( space+len, "cross");
	formation = space;
    }
return( formation );
}

int danceScottishRepetitions(Dance *dance) {

    if ( !danceHasReset(dance))
return( 1 );
    else if ( dance->choreo_cnt>0 )
return( 1 );

    switch ( dance->formation ) {
      case sp_scottishTripleMinor:
      case sp_scottishDupleMinor:
return( 8 );

      case sp_3Couple:
      case sp_4Couple:
      case sp_5Couple:
      case sp_6Couple:
      case sp_7Couple:
      case sp_8Couple:

      case sp_3CplCircle:
      case sp_squaredSet:
      case sp_5CplCircle:
      case sp_6CplCircle:
      case sp_7CplCircle:
      case sp_custom:
return( dance->role_cnt/2 );

      case sp_custom3:
      case sp_square3:
return( dance->role_cnt/3 );

      default:
      break;
    }
    if ( danceHasTag(dance,"sicilian"))
return( -1 );

return( 1 );
}

char *danceScottishPrecis(char *buffer, int blen, Dance *dance) {
    char *music;
    int bars;
    char *formation;
    int repetitions;

    if ( dance->rscds_precis )
return( dance->rscds_precis );

    music = danceMusicType(dance,true);
    bars = (int) (danceMinorSetActualMillibarCount(dance)/MILLIBARS_MUL);
    formation = danceScottishDanceType(dance);
    repetitions = danceScottishRepetitions(dance);

    if ( music==NULL || bars==0 )
return( NULL );

    if ( formation==NULL ) formation="";
    if ( repetitions<2 )
	snprintf(buffer,blen, "%s%d %s", music, bars, formation );
    else
	snprintf(buffer,blen, "%s%d\xC3\x97%d %s", music, repetitions, bars, formation );
return( buffer );
}
