/*
 *
 * critbl.c -- routines for dealing with boards and log2s
 *
 * This code is copyright 1995 by James A. Cherry, jac@doe.carleton.ca.
 * It is not to be redistributed or modified without his permission.
 *
 */

#include "funddefs.h"
#include "globals.h"
#include "proto.h"
#include "critique.h"

pent *GetCurrPtr( void )
/* Get pointer to current play. */
{
    if( PlayCurr == NULL ) return( PlayTail );
    else return( PlayCurr->prev );
}

void process_tiles_left( int num, playent *plptr )
{
    int i;

    if( plptr->turnscore > 0 ) {
	for( i = 0; i < plptr->cmove.placed; i++ )
	    PlrTiles[1 - num][i] = plptr->cmove.newlet[i].letter;
	for( ; i < 7; i++ )
	    PlrTiles[1 - num][i] = '\0';
    } else if( plptr->turnscore < 0 ) {
	for( i = 0; i < plptr->cmove.placed; i++ )
	    PlrTiles[num][i] = plptr->cmove.newlet[i].letter;
	for( ; i < 7; i++ )
	    PlrTiles[num][i] = '\0';
    }
}

void WalkThruPlayList( pent *final, int inclplay )
/* Walk through the list of plays up until the one pointed to by `final',
   adjusting the board and tracking as need be.  If `inclplay' is 1, also
   include the play from the `m' command. */
{
    pent *curr;
    playent *play;
    boardent *bptr;
    int pn, i, x, y;
    char tile;
    int foundp0, foundp1, FoundTurn;

    InitBoard();
    InitTiles();
    curr = PlayHead;
    while( curr != NULL && curr->prev != final ) {
	if( curr->ptype == PTYPE_PLAY ) {
	    play = curr->pptr.play;
/* I know this is cheesy, but it's easier to read. */
	  jumpin_point:
	    pn = play->num;
	    strcpy( PlrNm[pn], play->name );
	    PlrScores[pn] = play->newscore;
	    if( play->etype == LOG2_FORFEIT )
		strncpy( PlrTiles[pn], play->tiles, 7 );
	    else strncpy( PlrTiles[pn], play->tleft, 7 );
	    if( play->etype == LOG2_TILES_LEFT )
		process_tiles_left( pn, play );
	    if( play->etype != LOG2_MOVE ) goto dont_play;
	    for( i = 0; i < play->cmove.placed; i++ ) {
		x = play->cmove.newlet[i].x;
		y = play->cmove.newlet[i].y;
		tile = play->cmove.newlet[i].letter;
		if( tile == CH_BL ) {
		    Board[y][x] = play->cmove.newlet[i].blankchar;
		    if( B1X == 0 && B1Y == 0 ) {B1X = x; B1Y = y;}
		    else if( B2X == 0 && B2Y == 0 ) {B2X = x; B2Y = y;}
		    TilesLeft[26]--;
		} else {
		    Board[y][x] = tile;
		    TilesLeft[tile - 'A']--;
		}
	    }
	  dont_play:
	    if( play == PlayTemp ) {
		return;
	    }
	} else if( curr->ptype == PTYPE_BOARD ) {
	    InitBoard();
	    InitTiles();
	    bptr = curr->pptr.board;
	    B1X = bptr->b1x; B1Y = bptr->b1y;
	    B2X = bptr->b2x; B2Y = bptr->b2y;
	    for( x = 1; x < 16; x++ ) {
		for( y = 1; y < 16; y++ ) {
		    Board[y][x] = bptr->board[y][x];
		    if( x == B1X && y == B1Y ) TilesLeft[26]--;
		    else if( x == B2X && y == B2Y ) TilesLeft[26]--;
		    else if( Board[y][x] >= 'A' && Board[y][x] <= 'Z' )
			TilesLeft[Board[y][x] - 'A']--;
		}
	    }
	    strcpy( PlrNm[0], bptr->plrnm[0] );
	    strcpy( PlrNm[1], bptr->plrnm[1] );
	    strncpy( PlrTiles[0], bptr->plr_tiles[0], 7 );
	    strncpy( PlrTiles[1], bptr->plr_tiles[1], 7 );
	    PlrScores[0] = bptr->plr_scores[0];
	    PlrScores[1] = bptr->plr_scores[1];
	    MyPlrNum = bptr->myplrnum; TheirPlrNum = bptr->theirplrnum;
	} else if( curr->ptype != PTYPE_FIRST ) {
	    OPrint( "Unknown play type: %d\n", curr->ptype );
	    exit( -1 );
	}
	curr = curr->next;
    }
/* If we're to include the play at PlayTemp, check for it now. */
    if( inclplay == 1 ) {
	play = PlayTemp;
	goto jumpin_point;
    }
/* After walking up to play pointed to by final, see if any rack information
   exists for later plays. */
    foundp0 = 0; foundp1 = 0; FoundTurn = -1;
    while( curr != NULL && ( foundp0 == 0 || foundp1 == 0 ) ) {
	if( curr->ptype == PTYPE_BOARD ) break;
	if( curr->ptype != PTYPE_PLAY ) {
	    OPrint( "Unknown play type: %d\n", curr->ptype );
	    exit( -1 );
	}
	if( curr->pptr.play->etype == LOG2_TILES_LEFT )
	    process_tiles_left( curr->pptr.play->num, curr->pptr.play );
	if( curr->pptr.play->num == 0 && foundp0 == 0 ) {
	    foundp0 = 1;
	    if( curr->pptr.play->etype != LOG2_TILES_LEFT ) {
		strncpy( PlrTiles[0], curr->pptr.play->tiles, 7 );
	    }
	    PlrScores[0] = curr->pptr.play->oldscore;
	    strcpy( PlrNm[0], curr->pptr.play->name );
	    if( FoundTurn == -1 ) FoundTurn = 0;
	}
	if( curr->pptr.play->num == 1 && foundp1 == 0 ) {
	    foundp1 = 1;
	    if( curr->pptr.play->etype != LOG2_TILES_LEFT ) {
		strncpy( PlrTiles[1], curr->pptr.play->tiles, 7 );
	    }
	    PlrScores[1] = curr->pptr.play->oldscore;
	    strcpy( PlrNm[1], curr->pptr.play->name );
	    if( FoundTurn == -1 ) FoundTurn = 1;
	}
	curr = curr->next;
    }
    if( FoundTurn != -1 ) {
	MyPlrNum = FoundTurn;
	TheirPlrNum = 1 - FoundTurn;
    }
}

pent *CreateBoardEnt( void )
/* Create a play structure for a new board. */
{
    pent *p1;
    boardent *be;
    int i, j;

    p1 = (pent *) malloc( PENT_SIZE );
    p1->ptype = PTYPE_BOARD;
    be = (boardent *) malloc( BE_SIZE );
    p1->pptr.board = be;
    p1->next = NULL;
    p1->prev = NULL;
    for( i = 0; i < 16; i++ )
	for( j = 0; j < 16; j++ )
	    be->board[i][j] = Board[i][j];
    be->b1x = B1X; be->b1y = B1Y; be->b2x = B2X; be->b2y = B2Y;
    strcpy( be->plrnm[0], PlrNm[0] ); strcpy( be->plrnm[1], PlrNm[1] );
    strncpy( be->plr_tiles[0], PlrTiles[0], 7 );
    strncpy( be->plr_tiles[1], PlrTiles[1], 7 );
    be->plr_scores[0] = PlrScores[0]; be->plr_scores[1] = PlrScores[1];
    be->myplrnum = MyPlrNum; be->theirplrnum = TheirPlrNum;
    return( p1 );
}

int check_valid_play( void )
/* See if it was someone's turn on a cut-and-pasted board. */
{
    int i;
    char *c1, *c2;

    for( i = 0; i < LNum; i++ ) {
	if( ( c1 = strstr( Grid[i], "Waiting for" ) ) != NULL 
	    && ( c2 = strstr( Grid[i], "to play" ) ) != NULL ) {
            strncpy( PlrMove, c1 + 12, c2 - c1 - 13 );
            PlrMove[c2 - c1 - 13] = '\0';
            return( 1 );
	}
    }
    return( 0 );
}

void CritiqueBoard()
/* Deal with a cut-and-pasted board, setting up all data structures. */
{
    int s, i, j, tilep;
    char ch;
    pent *p1;
    boardent *be;

    InitBoard();
    InitTiles();
    FreeMoveList();
    p1 = CreateBoardEnt();
    be = p1->pptr.board;
    for( s = 0; strncmp( Grid[s], " 1|", 3 ) != 0; s++ );
    for( i = 1; i < 16; i++ ) {
	for( j = 1; j < 16; j++ ) {
	    ch = Grid[i + s - 1][( j - 1 ) * 2 + 3];
	    if( ch == '\'' ) be->board[i][j] = CH_DL;
	    else if( ch == '"' ) be->board[i][j] = CH_TL;
	    else if( ch == '-' ) be->board[i][j] = CH_DW;
	    else if( ch == '=' ) be->board[i][j] = CH_TW;
	    else if( ch >= 'A' && ch <= 'Z' ) be->board[i][j] = ch;
	    else if( ch >= 'a' && ch <= 'z' ) {
		be->board[i][j] = ch - 'a' + 'A';
		if( B1X == 0 && B1Y == 0 ) {
		    B1X = j; B1Y = i;
		    be->b1x = j; be->b1y = i;
		} else if( B2X == 0 && B2Y == 0 ) {
		    B2X = j; B2Y = i;
		    be->b2x = j; be->b2y = i;
		}
	    } else be->board[i][j] = CH_EM;
	}
    }
    sscanf( Grid[s + 1] + 38, "%s", be->plrnm[0] );
    sscanf( Grid[s + 2] + 38, "%s", be->plrnm[1] );
    tilep = 38;
    while( Grid[s + 1][tilep] != ' ' || Grid[s + 2][tilep] != ' ' ) tilep++;
    while( Grid[s + 1][tilep] == ' ' && Grid[s + 2][tilep] == ' ' ) tilep++;
    sscanf( Grid[s + 1] + tilep, "%7s", be->plr_tiles[0] );
    sscanf( Grid[s + 2] + tilep, "%7s", be->plr_tiles[1] );
    SortTiles( be->plr_tiles[0] );
    SortTiles( be->plr_tiles[1] );
    while( Grid[s + 1][tilep] != ' ' || Grid[s + 2][tilep] != ' ' ) tilep++;
    sscanf( Grid[s + 1] + tilep, "%d", &(be->plr_scores[0]) );
    sscanf( Grid[s + 2] + tilep, "%d", &(be->plr_scores[1]) );
    be->myplrnum = 0; be->theirplrnum = 0;
    if( check_valid_play() == 1 ) {
	if( strcmp( PlrMove, be->plrnm[1] ) == 0 ) be->myplrnum = 1;
	be->theirplrnum = 1 - be->myplrnum;
    }
    p1->prev = PlayTail;
    PlayTail->next = p1;
    PlayTail = p1;
    WalkThruPlayList( p1, 0 );
    PlayCurr = NULL;
    DispBoard( 0 );
}

void expand_log2( void )
/* Change a log2 from two columns to one, marking each entry according to
   which player it belongs to. */
{
    int i, l2l;

    ResetPlayList( 1 );
    sscanf( Grid[0], "%s", PlrNm[0] );
    sscanf( Grid[0] + 40, "%s", PlrNm[1] );
    for( l2l = 0, i = 1; i < LNum; i++ ) {
	if( ( Grid[i][0] != ' ' && Grid[i][0] != '\0' )
	    || ( Grid[i][14] != ' ' && Grid[i][14] != '\0' ) ) {
	    strncpy( ExpLog2[l2l], Grid[i], 38 );
	    ExpLog2[l2l][38] = '\0';
	    IndLog2[l2l++] = 0;
	}
	if( strlen( Grid[i] ) > 40
	    && ( ( Grid[i][40] != ' ' && Grid[i][40] != '\0' )
	    || ( Grid[i][54] != ' ' && Grid[i][54] != '\0' ) ) ) {
	    strncpy( ExpLog2[l2l], Grid[i] + 40, 38 );
	    ExpLog2[l2l][38] = '\0';
	    IndLog2[l2l++] = 1;
	}
    }
    Log2Len = l2l;
}

void check_connecting( char bc, int *nonconnect )
/* Set nonconnect to zero if bc is a letter. */
{
    if( bc >= 'A' && bc <= 'Z' ) *nonconnect = 0;
}

lerr_t extract_move( char *mstring, playent *plptr )
/* Extract the information from an actual play made by a player in the log2. */
{
    int i, j, placed;
    int mx, my, md, w;
    int overlap, nonconnect;
    char mxc;
    struct plrlet *newlet;

    overlap = 0; nonconnect = 1;
    for( i = 0; mstring[i] == ' '; i++ );
    sscanf( mstring + i, "%s", plptr->movels );
    if( mstring[i] >= '0' && mstring[i] <= '9' ) {
	sscanf( mstring + i, "%d%c", &my, &mxc );
	md = 0;
    } else {
	sscanf( mstring + i, "%c%d", &mxc, &my );
	md = 1;
    }
    mx = mxc - 'a' + 1;
    while( mstring[i] != ' ' ) i++;
    while( mstring[i] == ' ' ) i++;
    sscanf( mstring + i, "%s", plptr->movews );
    newlet = &(plptr->cmove.newlet[0]);
    strncpy( plptr->tleft, plptr->tiles, 7 );
    for( placed = 0; ( mstring[i] >= 'A' && mstring[i] <= 'Z' )
			 || ( mstring[i] >= 'a' && mstring[i] <= 'z' ); i++ ) {
	mxc = mstring[i];
	if( Board[my][mx] < 'A' || Board[my][mx] > 'Z' ) {
	    newlet[placed].x = mx;
	    newlet[placed].y = my;
	    newlet[placed].oldlet = Board[my][mx];
	    if( mxc >= 'A' && mxc <= 'Z' ) {
		newlet[placed].letter = mxc;
	    } else {
		newlet[placed].letter = CH_BL;
		newlet[placed].blankchar = mxc - 'a' + 'A';
	    }
	    for( j = 0; j < 7; j++ ) {
		if( plptr->tleft[j] == newlet[placed].letter ) {
		    newlet[placed].tilepos = j;
		    plptr->tleft[j] = '\0';
		    break;
		}
	    }
	    placed++;
/* Check if letter connects to existing puzzle. */
	    check_connecting( Board[my - 1][mx], &nonconnect );
	    check_connecting( Board[my + 1][mx], &nonconnect );
	    check_connecting( Board[my][mx - 1], &nonconnect );
	    check_connecting( Board[my][mx + 1], &nonconnect );
	} else {
/* If there is already a letter on the board in the current position, make
   sure it's the same letter as the log2 says it should be. */
	    if( mxc >= 'a' && mxc <= 'z' ) mxc -= ( 'a' - 'A' );
	    if( Board[my][mx] != mxc ) overlap = 1;
	}
	mx = mx + 1 - md; my = my + md;
    }
    plptr->cmove.next = NULL;
    plptr->cmove.placed = placed;
    plptr->cmove.dir = md;
    while( mstring[i] == ' ' ) i++;
    sscanf( mstring + i, "%d", &(plptr->l2turnscore) );
    CompressRack( plptr->tleft );
    w = FindNewWords( newlet, placed, 1 - md, md );
    plptr->wordsformed = w;
    for( i = 0, j = 0; i < w; i++ ) {
	strcpy( plptr->words[i], NewWords[i].letters );
	plptr->phony[i] = 1 - BinSearch( NewWords[i].letters );
	j += NewWords[i].score;
    }
    plptr->turnscore = j;
    if( overlap == 1 ) return( LERR_OVERLAP );
    if( nonconnect == 1 ) return( LERR_NONCONNECT );
    return( LERR_NONE );
}

lerr_t extract_dump( log2_t etype, char *mstring, playent *plptr )
/* Extract the information from an exchange in a log2. */
{
    int i, placed, j;
    struct plrlet *newlet;
    int baddump;

    baddump = 0; placed = 0;
    strncpy( plptr->tleft, plptr->tiles, 7 );
    if( etype == LOG2_EXCHANGE ) {
	i = 15;
	if( mstring[14] == 'd' ) i = 19;
	strcpy( plptr->movels, "xch" );
	newlet = &(plptr->cmove.newlet[0]);
	for( placed = 0;
	    ( mstring[i] >= 'A' && mstring[i] <= 'Z' )
		|| mstring[i] == CH_BL || mstring[i] == CH_UN; i++ ) {
	    plptr->movews[placed] = mstring[i];
	    newlet[placed].letter = mstring[i];
	    for( j = 0; j < 7; j++ ) {
		if( plptr->tleft[j] == mstring[i] ) {
		    newlet[placed].tilepos = j;
		    plptr->tleft[j] = '\0';
		    break;
		}
	    }
	    if( j == 7 ) baddump = 1;
	    else placed++;
	}
	plptr->movews[placed] = '\0';
	SortTiles( plptr->movews );
    } else if( etype == LOG2_PASS ) {
	strcpy( plptr->movews, "pass" );
	plptr->movels[0] = '\0';
    }
    plptr->cmove.next = NULL;
    plptr->cmove.placed = -placed;
    plptr->wordsformed = 0;
    plptr->turnscore = 0;
    sscanf( mstring + 29, "%d", &(plptr->l2turnscore) );
    CompressRack( plptr->tleft );
    if( baddump == 1 ) return( LERR_BADDUMP );
    return( LERR_NONE );
}

lerr_t extract_tiles_left( char *mstring, playent *plptr )
/* Extract the information from tiles left in a log2. */
{
    int i, j, score;

    score = 0;
    for( i = 15, j = 0; mstring[i] != ')'; i++ ) {
	score += Letters[Let2Ind( mstring[i] )].points;
	plptr->cmove.newlet[j++].letter = mstring[i];
    }
    plptr->cmove.next = NULL;
    plptr->cmove.placed = j;
    plptr->wordsformed = 0;
    sscanf( mstring + 29, "%d", &(plptr->l2turnscore) );
    if( plptr->l2turnscore > 0 ) plptr->turnscore = score * 2;
    else plptr->turnscore = -score;
    for( i = 0; i < 7; i++ ) plptr->tiles[i] = '\0';
    return( LERR_NONE );
}

void fill_in_scores( void )
/* Go through and fill in total scores after processing a log2. */
{
    int l2s[2], pn;
    playent *plptr;

    PlrScores[0] = -999; PlrScores[1] = -999;
    l2s[0] = -999; l2s[1] = -999;
    for( PlayCurr = PlayHead->next;
	PlayCurr != NULL; PlayCurr = PlayCurr->next ) {
	plptr = PlayCurr->pptr.play;
	pn = plptr->num;
	if( PlrScores[pn] == -999 ) PlrScores[pn] = 0;
	if( l2s[pn] == -999 ) l2s[pn] = 0;
	plptr->oldscore = PlrScores[pn];
	plptr->l2oldscore = l2s[pn];
	if( plptr->etype != LOG2_FORFEIT )
	    PlrScores[pn] += plptr->turnscore;
	plptr->newscore = PlrScores[pn];
	l2s[pn] = plptr->l2newscore;
    }
}

void build_from_log2( void )
/* Walk through a log2 and make a list of play structures. */
{
    int l2i, i;
    log2_t etype;
    pent *pe1;
    playent *pl1;

    for( l2i = 0; l2i < Log2Len; l2i++ ) {
	if( ExpLog2[l2i][10] != ' ' )
	    etype = LOG2_MOVE;
	else if( ( ExpLog2[l2i][14] == 'f' && ExpLog2[l2i][15] == 'o' )
		 || ( ExpLog2[l2i][14] == '-' && ExpLog2[l2i][15] == '-' ) )
	    etype = LOG2_FORFEIT;
	else if( ( ExpLog2[l2i][14] == 'p' && ExpLog2[l2i][15] == 'a' )
		 || ( ExpLog2[l2i][14] == '-' && ExpLog2[l2i][15] == ' ' ) )
	    etype = LOG2_PASS;
	else if( ( ExpLog2[l2i][14] == 'd' && ExpLog2[l2i][15] == 'u' )
		 || ( ExpLog2[l2i][14] == '-' && ExpLog2[l2i][15] != ' ' ) )
	    etype = LOG2_EXCHANGE;
	else if( ExpLog2[l2i][14] == '(' && ExpLog2[l2i][15] != '-' )
	    etype = LOG2_TILES_LEFT;
	else if( ( ExpLog2[l2i][14] == 't' && ExpLog2[l2i][15] == 'i' )
		 || ( ExpLog2[l2i][14] == '(' && ExpLog2[l2i][15] == '-' ) )
	    etype = LOG2_TIME_PENALTY;
	else {
	    OPrint( "Error: unknown log2 line\n" );
	    exit( -1 );
	}
	if( etype == LOG2_FORFEIT ) {
	    pl1 = PlayCurr->pptr.play;
	    sscanf( ExpLog2[l2i] + 34, "%d", &(pl1->l2newscore) );
	    pl1->etype = LOG2_FORFEIT;
	    Log2Err[l2i] = LERR_NONE;
	    continue;
	}
	pe1 = (pent *) malloc( PENT_SIZE );
	pe1->ptype = PTYPE_PLAY;
	pe1->prev = PlayTail;
	pe1->next = NULL;
	PlayTail->next = pe1;
	PlayTail = pe1;
	pl1 = (playent *) malloc( PE_SIZE );
	pe1->pptr.play = pl1;
	pl1->num = IndLog2[l2i];
	strcpy( pl1->name, PlrNm[pl1->num] );
	sscanf( ExpLog2[l2i], "%s", pl1->tiles );
	SortTiles( pl1->tiles );
	WalkThruPlayList( PlayTail, 0 );
	if( etype == LOG2_MOVE ) {
	    Log2Err[l2i] = extract_move( ExpLog2[l2i] + 7, pl1 );
	} else if( etype == LOG2_EXCHANGE || etype == LOG2_PASS ) {
	    Log2Err[l2i] = extract_dump( etype, ExpLog2[l2i], pl1 );
	} else if( etype == LOG2_TILES_LEFT ) {
	    Log2Err[l2i] = extract_tiles_left( ExpLog2[l2i], pl1 );
	} else if( etype == LOG2_TIME_PENALTY ) {
	    for( i = 0; i < 7; i++ ) pl1->tiles[i] = '\0';
	    sscanf( ExpLog2[l2i] + 29, "%d", &(pl1->turnscore) );
	    sscanf( ExpLog2[l2i] + 29, "%d", &(pl1->l2turnscore) );
	    Log2Err[l2i] = LERR_NONE;
	}
	sscanf( ExpLog2[l2i] + 34, "%d", &(pl1->l2newscore) );
	pl1->etype = etype;
	PlayCurr = pe1;
    }
    fill_in_scores();
}

void log2_error( char *estr, int eloc )
/* Display error when one exists in a log2. */
{
    if( BatchMode == BATCH_YES ) {
	printf( "log2 error: %s at `%s'\n", estr, ExpLog2[eloc] );
    } else {
	OPrint( "log2 error: %s at\n", estr );
	OPrint( "%s\n", ExpLog2[eloc] );
    }
}

int check_log2( void )
/* See if log2 has any errors in it. */
{
    return( Log2Len );
#if 0
    int i;

    PlayCurr = PlayHead;
    for( i = 0; i < Log2Len; i++ ) {
	if( Log2Err[i] == LERR_NONCONNECT && i != 0 ) {
	    log2_error( "Non-connecting move", i );
	    break;
	} else if( Log2Err[i] == LERR_OVERLAP ) {
	    log2_error( "Overlapping move", i );
	    break;
	} else if( Log2Err[i] == LERR_BADDUMP ) {
	    log2_error( "Invalid exchange", i );
	    break;
	}
	PlayCurr = PlayCurr->next;
    }
    if( i < Log2Len ) {
	WalkThruPlayList( PlayCurr, 0 );
	DispBoard( 0 );
	ResetPlayList( BatchMode == BATCH_YES ? 0 : 1 );
    }
    return( i );
#endif
}

int CritiqueLog2( void )
/* Deal with a cut-and-pasted log2. */
{
    int cl2;

    FreeMoveList();
    expand_log2();
    build_from_log2();
    if( ( cl2 = check_log2() ) == Log2Len ) {
	WalkThruPlayList( PlayHead, 0 );
	PlayCurr = PlayHead->next;
	if( BatchMode == BATCH_NO ) DispBoard( 0 );
    }
    return( cl2 );
}
