/*
 *
 * critlist.c -- routines for dealing with move and play lists
 *
 * 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"

/* The following variables are needed to mark the last of the best moves.
   This circumvents the problem in the endgame where the moves at the top
   of the list are winning outplays and are thus marked "best"; it is
   possible for other non-outplays to have higher or equal equity, but
   the ones with equal equity shouldn't also be labeled "best". */
int EndBestPage, EndBestLine;

char *frn( int sgn, int iv )
/* Find fractional value of a rack leave score. */
{
    char sgs[2];

    sgs[1] = '\0';
    if( iv < 0 ) { sgs[0] = '-'; iv = -iv; }
    else if( sgn == 1 ) sgs[0] = '+';
    else sgs[0] = '\0';
    sprintf( Frv, "%s%d.%d", sgs, iv / 2, ( iv % 2 == 0 ? 0 : 5 ) );
    return( Frv );
}

void check_if_theirs( compmove *cptr )
/* See if a move on the queue is the one actually made. */
{
    if( PlayCurr != NULL && PlayCurr->ptype == PTYPE_PLAY )
	if( strcmp( MoveLS, PlayCurr->pptr.play->movels ) == 0
	    && strcmp( MoveWS, PlayCurr->pptr.play->movews ) == 0 )
	    if( ThPl == NULL ) ThPl = cptr;
}

void adjust_outplay( compmove *cptr, int tl )
/* Fix the +100 outplay score. */
{
    if( tl > 14 ) return;
    if( cptr->winout == 1 ) cptr->score -= 200;
}

compmove *remove_entry( compmove **head, compmove *prev, compmove *curr )
/* Remove the move entry pointed to by curr. */
{
    compmove *ptemp;

    ptemp = curr->next;
    free( (compmove *) curr );
    if( prev == NULL ) *head = ptemp;
    else prev->next = ptemp;
    return( ptemp );
}

void opening_move_clean( void )
/* Clean redundant opening moves from the list. */
{
    compmove *pstart, *pend, *ptemp;
    char m1[16], m2[16];

/* First, find their move in the list. */
    for( pstart = CHead; pstart != NULL; pstart = pstart->next ) {
	ConvertMove( pstart );
	check_if_theirs( pstart );
    }
/* Then, go through list and mark all entries to be removed. */
    for( pstart = CHead; pstart != NULL; pstart = pend ) {
	for( pend = pstart;
	    pend != NULL && pend->score == pstart->score;
	    pend = pend->next );
	for( ; pstart->next != pend; pstart = pstart->next ) {
	    ConvertMove( pstart );
	    strcpy( m1, MoveWS );
	    MyToUpper( m1 );
	    for( ptemp = pstart->next;
		ptemp != NULL && ptemp != pend;
		ptemp = ptemp->next ) {
		ConvertMove( ptemp );
		strcpy( m2, MoveWS );
		MyToUpper( m2 );
		if( strcmp( m1, m2 ) == 0 ) {
		    if( ptemp != ThPl ) ptemp->score = -999;
		    else pstart->score = -999;
		}
	    }
	}
    }
/* Lastly, remove all marked entries. */
    for( pstart = NULL, pend = CHead; pend != NULL; ) {
	if( pend->score == -999 ) {
	    pend = remove_entry( &CHead, pstart, pend );
	} else {
	    pstart = pend; pend = pend->next;
	}
    }
}

void clean_move_list( void )
/* Clean unnecessary moves from the move list. */
{
    int i, tl;
    compmove *p1, *p2;
    char m1[MSTR_LEN], m2[MSTR_LEN];

    ThPl = NULL;
    if( Board[8][8] < 'A' ) {
	opening_move_clean();
	goto count_em;
    }
/* Check for same play, but with blanks in different place */
    for( tl = 0, i = 0; i < 27; i++ ) tl += TilesLeft[i];
    p2 = CHead;
    ConvertMove( p2 );
    check_if_theirs( p2 );
    adjust_outplay( p2, tl );
    strcpy( m2, MoveString );
    MyToUpper( m2 );
    if( p2 == NULL ) goto done_redundant;
    p1 = p2->next;
    while( p1 != NULL ) {
	ConvertMove( p1 );
	check_if_theirs( p1 );
	adjust_outplay( p1, tl );
	strcpy( m1, MoveString );
	MyToUpper( m1 );
	if( strcmp( m2, m1 ) == 0 && ThPl != p1 ) {
	    p2->next = p1->next;
	    free( (compmove *) p1 );
	    p1 = p2->next;
	} else {
	    strcpy( m2, m1 );
	    p2 = p1;
	    p1 = p1->next;
	}
    }
  done_redundant:
/* Remove vertical single tile plays, since they're already counted
   horizontally */
    for( p1 = NULL, p2 = CHead; p2 != NULL; ) {
	if( p2->placed == 1 && p2->dir == 1 && p2 != ThPl ) {
/* Next line is wrong: what if the move _isn't_ counted horiz. already? */
/*	    p2 = remove_entry( &CHead, p1, p2 ); */
	    p1 = p2; p2 = p2->next;
	} else {
	    p1 = p2; p2 = p2->next;
	}
    }
/* Count the number of moves on the cleaned list. */
  count_em:
    MCount = 0;
    if( ThPl == NULL ) ThNum = -1;
    for( p1 = CHead; p1 != NULL; MCount++, p1 = p1->next ) {
	if( p1 == ThPl ) ThNum = MCount;
    }
/* Count the number of best moves */
    EndBestLine = -1;
    for( p1 = CHead; p1 != NULL; p1 = p1->next ) {
	if( p1->score != CHead->score ) break;
	EndBestLine++;
    }
    EndBestPage = EndBestLine / MaxL;
    EndBestLine %= MaxL;
}

void make_page_list( void )
/* Make a list of where pages of moves begin and end. */
{
    compmove *p1;
    int i, j, score;

    MPages = ( MCount + MaxL - 1 ) / MaxL;
    if( MPages == 0 ) return;
    PageL = (compmove **) malloc( MPages * sizeof( compmove * ) );
    RankL = (int *) malloc( MCount * sizeof( int ) );
    score = -999;
    for( i = 0, j = 0, p1 = CHead; p1 != NULL; i++, p1 = p1->next ) {
	if( i % MaxL == 0 ) PageL[i / MaxL] = p1;
	if( p1->score != score ) {
	    score = p1->score;
	    j = i;
	}
	RankL[i] = j;
    }
}

void init_move_list( void )
{
    int i;

    sprintf( MHeader, "--------------------------------------------------" );
    MHeader[MoveWidth] = '\0';
    for( i = 0; i < MaxL; i++ ) MStr[i][0] = '\0';
}

void GenMoveList( void )
/* Generate a list of moves for displaying. */
{
    compmove *p1;
    int i, j, k, placed, f, chc;
    char tstr[20];
    int cw[6], cj[6], pcount, dodots;

    init_move_list();
    cj[0] = 1; cj[1] = 0; cj[2] = 0; cj[3] = 1; cj[4] = 1; cj[5] = 0;
    if( BatchMode == BATCH_NO ) {
	pcount = 2;
	cw[0] = 5; cw[1] = 3; cw[2] = 15; cw[3] = 3; cw[4] = 5; cw[5] = 7;
    } else {
	pcount = 1;
	for( f = 0; f < 6; f++ ) cw[f] = 0;
    }
    for( ; pcount < 3; pcount++ ) {
	for( i = 0, p1 = PageL[MPageNum]; i < MaxL && p1 != NULL;
					  i++, p1 = p1->next ) {
	    dodots = 0;
	    if( BatchMode == BATCH_YES ) {
		if( i == MaxL - 2 && ThNum >= MaxL ) {
		    dodots = 1;
		} else if( i == MaxL - 1 && ThNum >= MaxL ) {
		    p1 = ThPl;
		}
	    }
	    if( dodots == 0 ) ConvertMove( p1 );
/* Loop over fields */
	    chc = 0;
	    for( f = 0; f < 6; f++ ) {
		if( dodots == 1 ) {
		    if( f == 2 ) strcpy( tstr, "..." );
		    else tstr[0] = '\0';
		    goto made_string;
		}
		if( f == 0 ) {
		    if( MPageNum <= EndBestPage && i <= EndBestLine )
			sprintf( tstr, "best" );
		    else
			sprintf( tstr, "%s",
				 frn( 0, CHead->score - p1->score ) );
		} else if( f == 1 ) {
		    sprintf( tstr, "%s", MoveLS );
		} else if( f == 2 ) {
		    sprintf( tstr, "%s", MoveWS );
		} else if( f == 3 ) {
		    sprintf( tstr, "%d", ( p1->score - p1->rms ) / 2 );
		} else if( f == 4 ) {
		    sprintf( tstr, "%s", frn( 1, p1->rms ) );
		} else if( f == 5 ) {
		    strncpy( tstr, PlrTiles[MyPlrNum], 7 );
		    placed = p1->placed;
		    if( placed < 0 ) placed = -placed;
		    for( j = 0; j < placed; j++ )
			tstr[p1->newlet[j].tilepos] = ' ';
		    for( j = 0, k = 0; j < 7; j++ )
			if( tstr[j] != 0 && tstr[j] != ' ' )
			    tstr[k++] = tstr[j];
		    tstr[k] = '\0';
		}
	      made_string:
		if( pcount == 1 ) {
/* In first pass, adjust column widths */
		    if( strlen( tstr ) > cw[f] ) cw[f] = strlen( tstr );
		} else {
/* In second pass, fill in MStr[] */
		    if( cj[f] == 1 ) /* right align */
			for( j = 0; j < cw[f] - strlen( tstr ); j++ )
			    MStr[i][chc++] = ' ';
		    for( j = 0; j < strlen( tstr ); j++ )
			MStr[i][chc++] = tstr[j];
		    if( cj[f] == 0 ) /* left align */
			for( j = 0; j < cw[f] - strlen( tstr ); j++ )
			    MStr[i][chc++] = ' ';
/* Figure out separator character */
		    if( f == 0 && p1 == ThPl ) MStr[i][chc++] = '*';
		    else if( f == 3 && dodots == 0 ) MStr[i][chc++] = '/';
		    else if( f == 5 ) MStr[i][chc++] = '\0';
		    else MStr[i][chc++] = ' ';
		}
	    }
	}
    }
}

void calc_play_list_size( void )
{
    pent *p1;

    PCount = 0;
    for( p1 = PlayHead; p1 != NULL; p1 = p1->next, PCount++ );
    PPages = ( PCount + MaxL - 1 ) / MaxL;
    for( PNum = 0, p1 = PlayHead; p1 != GetCurrPtr();
				  PNum++, p1 = p1->next );
    PPageNum = PNum / MaxL;
}

void GenPlayList( void )
{
    pent *p1;
    playent *pl;
    boardent *bo;
    int i, j, p;

    init_move_list();
    for( p1 = PlayHead, i = PPageNum; i > 0; i-- )
	for( j = 0; j < MaxL; j++, p1 = p1->next );
    for( i = 0; i < MaxL && p1 != NULL; i++, p1 = p1->next ) {
	if( p1->ptype == PTYPE_FIRST ) {
	    sprintf( MStr[i], "   Start of play list" );
	} else if( p1->ptype == PTYPE_BOARD ) {
	    bo = p1->pptr.board;
	    sprintf( MStr[i], "   Board (1:" );
	    p = 12;
	    for( j = 0; j < 7; j++ )
		if( ( bo->plr_tiles[0][j] >= 'A'
		      && bo->plr_tiles[0][j] <= 'Z' )
		    || bo->plr_tiles[0][j] == CH_BL )
		    MStr[i][p++] = bo->plr_tiles[0][j];
	    sprintf( MStr[i] + p, " %3d, 2:", bo->plr_scores[0] );
	    p += 8;
	    for( j = 0; j < 7; j++ )
		if( ( bo->plr_tiles[1][j] >= 'A'
		      && bo->plr_tiles[1][j] <= 'Z' )
		    || bo->plr_tiles[1][j] == CH_BL )
		    MStr[i][p++] = bo->plr_tiles[1][j];
	    sprintf( MStr[i] + p, " %3d)", bo->plr_scores[1] );
	} else if( p1->ptype == PTYPE_PLAY ) {
	    pl = p1->pptr.play;
	    sprintf( MStr[i], "%d: ", pl->num + 1 );
	    if( pl->etype == LOG2_MOVE || pl->etype == LOG2_FORFEIT
		|| pl->etype == LOG2_EXCHANGE || pl->etype == LOG2_PASS ) {
		for( j = 0; j < 7; j++ )
		    if( ( pl->tiles[j] >= 'A' && pl->tiles[j] <= 'Z' )
			|| pl->tiles[j] == CH_BL ) {
			MStr[i][j + 3] = pl->tiles[j];
		    } else {
			MStr[i][j + 3] = ' ';
		    }
	    } else {
		sprintf( MStr[i] + 3, "       " );
	    }
	    MStr[i][10] = ' ';
	    if( pl->etype == LOG2_MOVE || pl->etype == LOG2_FORFEIT ) {
		if( pl->movels[0] >= 'a' && pl->movels[0] <= 'o' )
		    sprintf( MStr[i] + 11, "  %-3s ", pl->movels );
		else if( pl->movels[0] >= '1' && pl->movels[0] <= '9' )
		    sprintf( MStr[i] + 11, "%3s   ", pl->movels );
	    } else sprintf( MStr[i] + 11, "      " );
	    if( pl->etype == LOG2_MOVE || pl->etype == LOG2_FORFEIT ) {
		sprintf( MStr[i] + 17, "%-15s ", pl->movews );
	    } else if( pl->etype == LOG2_EXCHANGE ) {
		sprintf( MStr[i] + 17, "%s %-7s     ",
			 pl->movels, pl->movews );
	    } else if( pl->etype == LOG2_PASS ) {
		sprintf( MStr[i] + 17, "pass            " );
	    } else if( pl->etype == LOG2_TILES_LEFT ) {
		MStr[i][17] = '(';
		for( j = 0; j < pl->cmove.placed; j++ )
		    MStr[i][18 + j] = pl->cmove.newlet[j].letter;
		MStr[i][18 + j] = ')';
		for( ; j < 14; j++ ) MStr[i][19 + j] = ' ';
	    } else if( pl->etype == LOG2_TIME_PENALTY ) {
		sprintf( MStr[i] + 17, "time penalty    " );
	    }
	    sprintf( MStr[i] + 33, "%+4d %4d", pl->turnscore, pl->newscore );
	    if( pl->etype == LOG2_FORFEIT ) MStr[i][37] = '*';
	}
	for( j = strlen( MStr[i] ); j < MoveWidth; j++ ) MStr[i][j] = ' ';
	MStr[i][j] = '\0';
	if( p1 == GetCurrPtr() ) MStr[i][2] = '*';
    }
}

void FreeMoveList( void )
/* Free list of moves and all associated data structures. */
{
    compmove *tptr, *uptr;

    tptr = CHead;
    while( tptr != NULL ) {
	uptr = tptr->next;
	free( (compmove *) tptr );
	tptr = uptr;
    }
    CHead = NULL;
    if( PageL != NULL ) {
	free( (compmove **) PageL );
	PageL = NULL;
    }
    if( RankL != NULL ) {
	free( (int *) RankL );
	RankL = NULL;
    }
    init_move_list();
    MNum = -1;
    MoveWindowMode = MWM_MOVES;
    UndoM( 0 );
    if( FullScreen == FS_YES ) {
	werase( Wind[W_MOVES] );
	MHeaderLine();
	wrefresh( Wind[W_MOVES] );
	CursorRefresh();
    }
}

void free_play_list( void )
/* Free list of plays. */
{
    pent *pe1, *pe2;

    for( pe1 = PlayHead; pe1 != NULL; ) {
	pe2 = pe1->next;
	if( pe1->ptype == PTYPE_PLAY )
	    free( (playent *) pe1->pptr.play );
	else if( pe1->ptype == PTYPE_BOARD )
	    free( (boardent *) pe1->pptr.board );
	free( (pent *) pe1 );
	pe1 = pe2;
    }
    PlayHead = NULL; PlayTail = NULL; PlayCurr = NULL;
}

void ResetPlayList( int remake )
/* Reset list of plays. */
{
    free_play_list();
    if( remake == 0 ) return;
    strcpy( PlrNm[0], "Player 1" );
    strcpy( PlrNm[1], "Player 2" );
    PlrScores[0] = 0; PlrScores[1] = 0;
    InitBoard();
    InitTiles();
    MyPlrNum = 0; TheirPlrNum = 1;
    PlayHead = (pent *) malloc( PENT_SIZE );
    PlayHead->ptype = PTYPE_FIRST;
    PlayHead->pptr.board = NULL;
    PlayHead->prev = NULL;
    PlayHead->next = NULL;
    PlayTail = PlayHead;
    PlayCurr = NULL;
}

void clean_tiles( char *tiles )
/* Make sure invalid tiles are removed from the rack. */
{
    int i;

    for( i = 0; i < 7; i++ ) {
	if( ( tiles[i] >= 'A' && tiles[i] <= 'Z' ) || tiles[i] == CH_BL )
	    continue;
	if( tiles[i] >= 'a' && tiles[i] <= 'z' ) tiles[i] -= ( 'a' - 'A' );
	else tiles[i] = '\0';
    }
}

void ShowMoveList( void )
/* Find moves, given board, racks, and scores, and show the list. */
{
    char ptt[7];

    if( MyPlrNum == 0 && TheirPlrNum == 0 ) {
	OPrint( "It is nobody's turn.\n" );
	return;
    }
    if( CHead == NULL ) {
	CalcHooks();
	strncpy( ptt, PlrTiles[MyPlrNum], 7 );
	clean_tiles( PlrTiles[MyPlrNum] );
	if( PlayCurr != NULL && PlayCurr->ptype == PTYPE_PLAY ) {
	    if( PlayCurr->pptr.play->movels[0] >= 'a' &&
		PlayCurr->pptr.play->movels[0] <= 'o' )
		VerticalPlay = 1;
	    else
		VerticalPlay = 0;
	}
	ComputerMove( MyPlrNum );
	PassScore();
	strncpy( PlrTiles[MyPlrNum], ptt, 7 );
	clean_move_list();
	make_page_list();
	MPageNum = 0;
	MNum = 0;
    }
    MoveWindowMode = MWM_MOVES;
    KeyMode = MODE_MOVES;
    PrintMoveList();
}

void ShowPlayList( void )
/* Display the play list. */
{
    UndoM( 1 );
    calc_play_list_size();
    MoveWindowMode = MWM_PLAYS;
    KeyMode = MODE_MOVES;
    PrintMoveList();
}
