/*
    This is a modification of the scrabble move finder 'findmoves.c' also
   in this directory.

   This one however attempts to place a move on an 'upwords' board.  See the
   scrabble version for common comments.  At the moment, it appears to
   be working.  The basic engine took about 2 hours to convert from the
   Scrabble source, the biggest difference being that the placement loop
   was changed to a recursive procedure.  This allowed multiple variations
   on placements at each row/col address, as was required by the choice
   of placing a tile on an existing stack - or not.  I'm currently adding
   scoring and some tile management to actually make it play a game.

   Note: upwords has a single "Qu" tile, so the easiest way to handle
   this is to preprocess the word list: remove all words where a Q
   is not followed by a U, then conflate all QU's to a single Q.  Remember
   to output any Q's as "Qu" if doing a fancy display.

   There appear to be AT LEAST two sets of rules for 'upwords', so the
   rules and scoring adhered to in this program will definitely not please
   everyone, at least until I parameterise the game for both variations.
   (One is from a US Milton Bradley 10x10 board, the other is from a UK Parker
   Bros 10x10 board, travel version)

   Meanwhile, let's use these rules:
   http://www.backgammon.com/consumer/instruct/upwords.pdf

   Note: is is almost possible to work out the score just from the
   pattern of the word that is to be played at any position, ignoring
   the actual letters that would be played.  ('almost' is because there
   is a small bonus if a "Qu" is played).  It should be possible to
   trim the search using this fact, so that we don't waste time
   enumerating possible words for a slot if we already know that the
   score can't be any higher than one we have already looked at.
   This should speed up move generation considerably.

   PENDING:
   Scoring is not quite right yet: haven't handled "Qu" bonus yet.
   Need to tidy code where bonus 20 is added for a bingo
   Still to add code to reject plurals.
   Need to refine code which implements this rule:

     Change a word already on the board to a different word.
     If a word is changed, at least one letter of the original
     word must be used in the new word.

   Strategy:  (according to hints on the MSN gaming zone...)

     Play shorter words than you might play in a Scrabble game. 
     Build upward (or upword!) before building outward, as you might in a
      Scrabble game. 
     Try to place tiles on the corner squares. 
     Try to place the fifth (topmost) tile on a stack. 
     Get rid of bad tiles quickly. 
     Learn to identify two-letter words. 
     Avoid building adjacent words unless you are a master UpWords player;
      your total score will be lower in the long run. 
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

#include "splib.h"

#ifndef FALSE
#define TRUE (0==0)
#define FALSE (0!=0)
#endif

int spell_verbose = FALSE;

static int debug = FALSE;

#define WIDTH 10
#define HEIGHT 10
#define RACKSIZE 7
#define MAX_TILES 100

/* 1..WIDTH for board, 0 and (WIDTH+1) for tombstones */
/* these two are swapped x/y with orientation */
static char board[(HEIGHT+2)][(WIDTH+2)];  /* row, col format */
static char height[(HEIGHT+2)][(WIDTH+2)];
/* these two are recalculated on each orientation so don't need to be swapped */
static char *crosschecks[(HEIGHT+2)][(WIDTH+2)];
static int vertical_score[(HEIGHT+2)][(WIDTH+2)];

static char remaining_tiles[MAX_TILES+1];
static int score, tiles_left_in_bag;

static int tiles_held = 0;
static char tiles[RACKSIZE+1];

/* To do: when calculating crosschecks, work out how much putting a tile
   on the crosscheck square will get you, from the vertical component.
   Pre-compute all but the last part (i.e. the value of the placed tile) */

#define HORIZONTAL 1
#define VERTICAL 2

static NODE *dawg;
static INDEX nedges;

#define swap(a,b,temp) {temp = a; a = b; b = temp;}

/*--------------------- crosscheck code -------------------------*/

static int crosscheck_internal(
  NODE *dawg, INDEX i,
  char *word, char *res, char *set,
  int len, int *found
)
{
int endsword, last, ch, target;
NODE node;
INDEX link;
static int wildch = '\0';
  for (;;) {
    node = dawg[i++];
    ch = (int)((node >> V_LETTER) & M_LETTER);
    last = ((node & (INDEX)M_END_OF_NODE) != 0);
    endsword = ((node & M_END_OF_WORD) != 0);
    link = node & M_NODE_POINTER;

    res[len] = ch; res[len+1] = '\0';
    target = ((int)*word)&255;
    if (ch != 0) {
    if (ch == target || target == '?') {
      if (target == '?') {
        wildch = ch;
      }
      if (endsword && *(word+1) == '\0') {
        int i;
        (*found)++;
        /* Add ch to crosscheck set if not already there */
        /* We just assume only 1 '?' per word, and last wildch was it */
        if (strchr(set, wildch) == NULL) {
          i = strlen(set);
          set[i] = wildch; set[i+1] = '\0';
	}
      }
      if (*(word+1) != '\0' && link != 0)
        (void) crosscheck_internal(dawg, link, word+1, res, set, len+1, found);
    }
    }
    if (last) break;
  }
  return(0==0);
}

char *convert_wildcard_to_permitted_set(NODE *dawg, char *word)
{
char result[MAX_WORD_LEN];
static char set[MAX_WORD_LEN];
int i = 0;
  *set = '\0';
  if (debug) fprintf(stdout, "wildcard: %s\n", word);
  (void)crosscheck_internal(dawg, (INDEX)ROOT_NODE, word, result, set, 0, &i);
  if (i == 0) return(NULL);
  if (debug) fprintf(stdout, "          -> %s\n", set);
  return(set);
}

/*--------------------- placement code -------------------------*/

static void fix_upwords(
  NODE *dawg, INDEX i,
  char *word, char *res, char *tilesleft,
  int len, int *found, int L, int n,
  int hscore, int vscore, int orientation,
  int word_is_flat
)
{
int endsword, last, ch, target;
NODE node;
INDEX link;

  for (;;) {
    node = dawg[i++];
    ch = (int)((node >> V_LETTER) & M_LETTER);
    last = ((node & (INDEX)M_END_OF_NODE) != 0);
    endsword = ((node & M_END_OF_WORD) != 0);
    link = node & M_NODE_POINTER;

    res[len] = ch; res[len+1] = '\0';
    target = *word; target &= 255;
    if (ch != 0) {
      if (ch == target) { /* matches a tile on the board already */
        if (endsword && *(word+1) == '\0') {
          fprintf(stdout, "play: %s at %c%d %s for %d%s\n", res, L, n,
            (orientation == HORIZONTAL ? "across" : "down"),
            (word_is_flat ? 2*hscore : hscore)+vscore,
            (word_is_flat ? " (plus word is flat - bonus points)" : ""));
          (*found)++;
        }
        if (*(word+1) != '\0' && link != 0) {
          fix_upwords(dawg, link, word+1, res, tilesleft,
            len+1, found, L, n, hscore, vscore, orientation,
            word_is_flat);
	}
      } else if (target == '[') { /* Is this letter in our set of valid letters? */
        char choices[(WIDTH * (256+2)) + 1];
        char *s, *saved = word;
        strcpy(choices, word+1);
        s = strchr(choices, ']'); /* We assume well-formed expressions */
        *s = '\0';
        word = strchr(word, ']');
        if (strchr(choices, ch) != NULL) {
          if (endsword && *(word+1) == '\0') {
            char *s;
            int i;
            s = strchr(tilesleft, ch);
            if (s != NULL) { /* Do we have the actual tile? */
              i = *s;
              *s = tilesleft[0]; tilesleft[0] = i;
              fprintf(stdout, "play: %s at %c%d %s for %d%s\n", res, L, n,
                (orientation == HORIZONTAL ? "across" : "down"),
                (word_is_flat ? 2*hscore : hscore)+vscore,
                (word_is_flat ? " (word is flat - bonus points)" : ""));
              (*found)++;
	    }
          }
          if (*(word+1) != '\0' && link != 0) {
            char *s;
            int i;
            s = strchr(tilesleft, ch);
            if (s != NULL) { /* Do we have the actual tile? */
              i = *s;
              *s = tilesleft[0]; tilesleft[0] = i;
              fix_upwords(dawg, link, word+1, res, tilesleft+1,
                len+1, found, L, n, hscore, vscore, orientation, word_is_flat);
	    }
	  }
	}
        word = saved;
      }
    }
    if (last) break;
  }
}

void upwords_match(
  NODE *dawg, char *word, char *tiles, int L, int n, int orientation,
  int word_is_flat, int hscore, int vscore)
{
  int i = 0;
  char result[MAX_WORD_LEN];
  result[0] = '\0';

  if (debug) fprintf(stdout, "match: '%s' using '%s' at %c%0d %s\n", word, tiles, L, n,
    (orientation == HORIZONTAL ? "across" : "down"));

  fix_upwords(dawg, (INDEX)ROOT_NODE, word, result, tiles, 0, &i, L, n,
              hscore, vscore,
              orientation, word_is_flat);
}

/*--------------------------------------------------------------*/
/*
   Board layout is different from Scrabble.
 */

void read_board(char *fname)
{
  int row, col;
  int c;
  FILE *sample;

  sample = fopen(fname, "r");
  if (sample == NULL) {
    fprintf(stderr, "Cannot open board file '%s'\n", fname);
    exit(0);
  }
  for (row = 1; row <= WIDTH; row++) {
    if (debug) fprintf(stderr, "Row %02d: ", row);
    for (col = 1; col <= WIDTH; col++) {
      height[row][col] = 0;
      c = fgetc(sample);
      if (c == ' ') {
        c = fgetc(sample);
      }
      if (isdigit(c)) {
        height[row][col] = c-'0';
        c = fgetc(sample);
      }
      if (isalpha(c)) { /* Take care with locale for other language versions */
        if (height[row][col] == 0) height[row][col] = 1;
        board[row][col] = tolower(c);
        if (debug) fprintf(stderr, "%c", tolower(c));
      } else if (debug) fprintf(stderr, " ");
    }
    c = fgetc(sample); /* newline */
    if (c != '\n') {
      fprintf(stderr, "Data format in .dat file\n");
      exit(0);
    }
    if (debug) fprintf(stderr, "\n");
  }
  tiles_held = 0;
  for (col = 0; col < RACKSIZE; col++) {
    c = fgetc(sample);
    if (c == '?') continue;
    if (c == '*') c = '?';
    if (isalpha(c)) c = tolower(c);
    tiles[tiles_held++] = c;
  }
  tiles[tiles_held] = '\0';
  fscanf(sample, " %d %d\n", &score, &tiles_left_in_bag);
  if (debug) {
    fprintf(stderr, "%0d Tiles: %s\n", tiles_held, tiles);
  }
  remaining_tiles[0] = '\0';
  fgets(remaining_tiles, MAX_TILES+1, sample); /* Don't care if error */
  fclose(sample);
}

/*--------------------------------------------------------------*/
/*
    In 'upwords', you can place a letter on an existing tile, so unlike
    scrabble, this procedure is not called only for blank spaces.  However
    if tiles are already stacked 5 deep, no more tiles may be placed
    here, so the fixed tile is added to the pattern, not a restricted
    wild-card set (eg "s" instead of "[s]") as no tile may be placed here.
 */

char *create_crosscheck_wildcard(int r, int c)
{
  char crosscheck[(WIDTH+2)];
  char *s;
  int rp;
  int vscore = 0;
  int flat = TRUE;

  /* Already tiles here 5 deep so crosscheck is meaningless */
  if (height[r][c] == 5) return(NULL);

  /* none above and none below? */
  if ((board[r-1][c] == 0) && (board[r+1][c] == 0)) return(NULL);

  rp = r-1;
  while (board[rp][c] != 0) {
    vscore += height[rp][c];
    if (height[rp][c] > 1) flat = FALSE;

    // do i need to port this bugfix that I made to scrabble?
    // was getting blank as a wildcard, not as a fixed letter...
    //if (*s == '?') *s = board[rp][c]; // FIX FOR BUG IN "wildcard:"

    rp -= 1;   /* what's above? */
  }
  rp += 1; /* row of 1st letter in crosscheck word */
  s = crosscheck; while (rp != r) *s++ = board[rp++][c];
  vscore += height[r][c];
  if (height[r][c] > 0) flat = FALSE;
  *s++ = '?'; /* r */
  rp = r+1;
  while ((*s++ = board[rp][c]) != '\0') {
    vscore += height[rp][c];
    if (height[rp][c] > 1) flat = FALSE;

    // do i need to port this bugfix that I made to scrabble?
    // was getting blank as a wildcard, not as a fixed letter...
    //if (*s == '?') *s = board[rp][c]; // FIX FOR BUG IN "wildcard:"

    rp += 1;
    /* what's below? */;
  }
  if (flat) vscore = vscore * 2 + 1; /* two points per tile if flat */
  vertical_score[r][c] = vscore; /* NOT including this tile */
  return(strdup(crosscheck));
}

/*--------------------------------------------------------------*/

/* It should be possible to add up most of the score using only the
   template from 'slot()' rather than the actual word.  A small adjustment
   can be made at the end for playing a "Qu" etc. */

void slot_internal(int orig_r, int orig_c,
                  int r, int c,
                  int still_to_place, int placed,
                  char *pattern,    /* wild-card pattern, built on fly */
                  int touches_center, int touches_horiz,
                  int touches_vert, int touches_below,
                  int tile_showing, int word_is_flat,
                  int orientation, int hscore, int vscore)
{
  int valid;
  int pp = strlen(pattern);
  char *s;

  /* No more loop.  Just place one, and recurse.  If I had done this
     with the scrabble program to begin with, I wouldn't have had the
     stupid bug to do with playing blanks as the same letter as another
     letter you hold in the rack... */

  /* Ran off end of board with tiles still to place? */
  if ((c > WIDTH) && (still_to_place > 0)) return;

  if (height[r][c] == 5) {
    int i;
    /* There is already a tile on the board here. */
    pattern[pp++] = board[r][c]; pattern[pp] = '\0';
    slot_internal(orig_r, orig_c,
                  r, c+1, still_to_place, placed, pattern,
                  touches_center, touches_horiz,
                  touches_vert, touches_below,
                  TRUE, FALSE, orientation, hscore+height[r][c], vscore);
                              /* add the horizontal component of the score */
    pattern[--pp] = '\0'; /* undo recursion */
    return;
  } else if (height[r][c] > 0) {
    int i;
    /* There is already a tile on the board here, but we're going to skip it */
    pattern[pp++] = board[r][c]; pattern[pp] = '\0';
    slot_internal(orig_r, orig_c,
                  r, c+1, still_to_place, placed, pattern,
                  touches_center, touches_horiz,
                  touches_vert, touches_below,
                  TRUE, (height[r][c] > 1 ? FALSE : word_is_flat),
                  orientation, hscore+height[r][c], vscore);
    pattern[--pp] = '\0'; /* undo recursion */
    /* Now try again with a tile on top of this letter or empty square */
    word_is_flat = FALSE;
    hscore += (height[r][c]+1);
    if (vertical_score[r][c] != 0) vscore += (vertical_score[r][c] + 1);
  } else {
    /* single tile being placed on board, none underneath */
    hscore += 1;
    if (vertical_score[r][c] != 0) vscore += (vertical_score[r][c] + 1);
  }

  /* square is either empty or has < 5 tiles on it */

  /* If center squares are empty this must be the first move */
  if ((board[WIDTH/2][HEIGHT/2] == 0) && (board[(WIDTH/2)+1][(HEIGHT/2)] == 0)
       && (board[WIDTH/2][(HEIGHT/2)+1] == 0) && (board[(WIDTH/2)+1][(HEIGHT/2)+1] == 0)
       && ((WIDTH/2 <= r) && (r <= (WIDTH/2)+1))
       && ((WIDTH/2 <= c) && (c <= (WIDTH/2)+1))
     ) {
    touches_center = (touches_center || TRUE);
  }
  if (board[r][c-1] != 0 || board[r][c+1] != 0) touches_horiz = (touches_horiz || TRUE);
  if (board[r-1][c] != 0 || board[r+1][c] != 0) touches_vert = (touches_vert || TRUE);
  if (board[r][c] != 0) touches_below = (touches_below || TRUE);

  if (crosschecks[r][c] == NULL) {
    crosschecks[r][c] = strdup(tiles);
  }

  /* If no valid crosscheck at all, then reject this slot */
  if (*crosschecks[r][c] == '!') return;
  assert(strcmp(crosschecks[r][c], "?") != 0);

  if ((board[r][c] != 0) && ((s = strchr(crosschecks[r][c], board[r][c])) != NULL)) {
    /* Don't allow tile to be placed on top of same letter */
    *s++ = '\0'; sprintf(pattern+pp, "[%s%s]", crosschecks[r][c], s); *--s = board[r][c];
    if (strcmp(pattern+pp, "[]") == 0) return;
  } else {
    sprintf(pattern+pp, "[%s]", crosschecks[r][c]);
  }
  pp = strlen(pattern);
  placed += 1; still_to_place -= 1;

  if (still_to_place != 0) {
    slot_internal(orig_r, orig_c,
                  r, c+1, still_to_place, placed, pattern,
                  touches_center, touches_horiz,
                  touches_vert, touches_below,
                  tile_showing, word_is_flat,
                  orientation, hscore, vscore);
    pattern[pp] = '\0';
    return;
  }

  /* after placing all tiles, are there still some letters abutting
      to the right?  If so, add them to the string to be matched */
  {int p = c+1; while (board[r][p] != 0) {
    hscore += height[r][p];
    if (height[r][p] > 1) word_is_flat = FALSE;
    touches_horiz = TRUE; tile_showing = TRUE;
    pattern[pp++] = board[r][p++];
  }}
  pattern[pp] = '\0';

  valid = (touches_horiz ||
           (touches_vert && (placed > 1)) ||
           (touches_center && (placed > 1))); /* Eliminate any plays totally on top of another word */

  if (touches_below) {
    /* at least one new tile is stacked on an existing tile */
    /* and there are no tiles in the new word which *don't* have a tile
       placed on top of them */
    if (!tile_showing) valid = FALSE;
/* NOTE: We have a problem here:

                      ??F??
                        R
                        E
                        DAVID

   If we play a word over ??F??, according to the rules if you cover
   a word completely, it's not a valid play.  Well, 'F' is not a
   word, and this is not what the rule is meant to block, but it's
   hard for the program to see that.
 
   For instance,

                      ??F?S
                        R T
                        E E
                        DAVID
                          E

  if we play 5 tiles over ??F?S, this too is probably allowed, BUT:

                      ??FJ?
                        RE
                        EM
                        DAVID

  playing over this pattern "??FJ?"  (let's assume FJ is a valid word)
  should NOT be allowed.  So the test must be that there are two abutting
  tiles being covered up somewhere within the placement.  Not impossible
  to define, but tricky coding that I haven't done yet.

*/
  }

  /* In upwords we ban words which are formed by appending just an S */
  if (touches_horiz && (placed == 1)) {
    /* TO DO: because we haven't yet done final word generation, we can't
       just add a check for a final -s and discard the word if present;
       what we have to do is look at a word where we may be adding the
       final -s, and remove that option. 

       eg if our pattern is "word[sy]" then we convert it to "word[y]",
       but if our pattern is "word[s]" then when we convert it to "word[]"
       we *can* remove it from our options.
     */
  }

  /* At this point we COULD not bother to walk the dawg if we can already
     tell that the maximum score possible is less than some existing try */
  
  if (valid) upwords_match(dawg, pattern, tiles,
    (orientation == HORIZONTAL ? orig_c : orig_r)-1+'A',
    (orientation == HORIZONTAL ? orig_r : orig_c), orientation,
    word_is_flat, hscore, vscore + (placed == 7 ? 20 : 0));
         /* vscore is not the ideal way to pass in the bingo bonus */
}

void slot(int r, int c, int still_to_place, int orientation) /* wrapper */
{
  int touches_center = FALSE;
  int touches_horiz = FALSE;
  int touches_vert = FALSE;
  int touches_below = FALSE;
  int tile_showing = FALSE;
  int word_is_flat = TRUE;
  char pattern[(WIDTH * (256+2)) + 1];
  int placed=0;

  pattern[0] = '\0';

  /* Covered with an earlier hook */
  if (board[r][c-1] != 0) return;

  return(slot_internal(r, c,
                       r, c, still_to_place, placed, pattern,
                       touches_center, touches_horiz,
                       touches_vert, touches_below,
                       tile_showing, word_is_flat,
                       orientation, 0, 0));
}

int main(int argc, char **argv)
{
  int orientation = 0;
  int row = 0, col = 0;
  int length = 0;
  char *dict = NULL, *boardfile = NULL;

  if (argc != 3) {
    if (argc == 2) {
      dict = "twl98-q"; /* Tweaked for "Qu" -> "q" */
    } else {
      fprintf(stderr, "syntax: %s board.dat dict?\n", argv[0]);
      exit(1);
    }
  } else dict = argv[2]; /* Default word list is TWL98 */
  boardfile = argv[1];

  if (!dawg_init(dict, &dawg, &nedges)) {
    fprintf(stderr, "%s: cannot open wordlist '%s'\n", argv[0], dict);
    exit(2);
  }

  for (row = 0; row < (WIDTH+2); row++) {  /* Clear the arrays */
    for (col = 0; col < (WIDTH+2); col++) {
      board[row][col] = height[row][col] = 0;
      crosschecks[row][col] = NULL;
    }
  }

  read_board(boardfile);

  for (orientation = HORIZONTAL; orientation <= VERTICAL; orientation++) {
    for (row = 1; row < (HEIGHT+1); row++) {
      for (col = 0; col < (WIDTH+2); col++) {
        vertical_score[row][col] = 0;
        if (crosschecks[row][col] != NULL) free(crosschecks[row][col]);
        crosschecks[row][col] = NULL;
      }
      for (col = 1; col < (WIDTH+1); col++) {
        if (crosschecks[row][col] != NULL) free(crosschecks[row][col]);
        crosschecks[row][col] = create_crosscheck_wildcard(row, col);

        if (crosschecks[row][col] == NULL) {
          crosschecks[row][col] = strdup(tiles); /* was strdump("?") */
        } else {
          char *s = convert_wildcard_to_permitted_set(dawg, crosschecks[row][col]);
          free(crosschecks[row][col]);
          crosschecks[row][col] = strdup((s == NULL) ? "!" : s);
        }
      }

      for (col = 1; col < (WIDTH+1); col++) {
        for (length = 1; length <= tiles_held; length++) {
          if (debug) fprintf(stdout, "Testing %c%d %s: %d\n", 
            (orientation == HORIZONTAL ? col : row)-1+'A',
            (orientation == HORIZONTAL ? row : col),
            (orientation == HORIZONTAL ? "across" : "down"),
            length);
          slot(row, col, length, orientation);
        }
      }
    } /* Try every row on the board */

    /* flip the board around the x=y diagonal */

    assert(WIDTH == HEIGHT);
    for (row = 1; row < (WIDTH+1); row++) {
      for (col = 1; col < (WIDTH+1); col++) {
        int i;
        if (row > col) { /* avoid doing twice (which would be a no-op) */
          swap(board[row][col], board[col][row], i);
          swap(height[row][col], height[col][row], i);
        }
      }
    }
  } /* end of horiz/vert placement loop */

  {int i; FILE *sample;
  sample = fopen(boardfile, "w"); if (sample == NULL) exit(1);
  /* If want to make best play on board and save it, do it here */
  for (row = 1; row < HEIGHT+1; row++) {
    for (col = 1; col < WIDTH+1; col++) {
      if (height[row][col] <= 1) fputc(' ', sample); else fputc('0'+height[row][col], sample);
      if (height[row][col] > 0) fputc(toupper(board[row][col]), sample); else fputc('.', sample);
    }
    fputc('\n', sample);
  }
  for (i = 0; i < strlen(tiles); i++) fputc(toupper(tiles[i]), sample);
  for (i = strlen(tiles); i < 7; i++) fputc('?', sample);
  fprintf(sample, " %d %d\n", score, tiles_left_in_bag);
  if (*remaining_tiles != '\0') fprintf(sample, "tiles: %s\n", remaining_tiles);
  fclose(sample);
  }
  exit(0);
}