// TO DO: replace local copy with shared code from function.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // sleep()
#include <ctype.h>  // isprint()
#include <error.h>
#include <errno.h>

extern int solvable(char *p, int *steps);

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

//#define MONITOR TRUE

#define UP 0
#define LEFT 0
#define BACKWARDS 0

#define DOWN 1
#define RIGHT 1
#define FORWARD 1

#define NUM_ROWS 6
#define NUM_COLS 6

#define MAX_POSITIONS  65536 /* Use 32768 if that's too much RAM for you */
#define MAX_MOVES 128 /* TEMP ONLY!!! */
#define BOARD_SIZE (NUM_ROWS*NUM_COLS + MAX_MOVES + 1 + 1) // Unless we add move history to each queued board! */

static int screen[37][19];

void draw_block(char *p, int ox, int oy, int horizontal, int block) {
  int i,j, x,y, w,h;
  // always at least 2, sometimes 3
  x = ox*6; y = oy*3;
  if (p[oy*6+ox] != block) {
    fprintf(stderr, "p[%d][%d] = %c, block = %c\n", ox, oy, p[oy*6+ox], block);
  }
  if (horizontal) {
    h = 3; w = 12;                                // GG
    if ((ox < 4) && (p[oy*6+ox+2] == block)) w = 18; // GGG
  } else {
    h = 6; w = 6;
    if ((oy < 4) && (p[(oy+2)*6+ox] == block)) h = 9;
  }
  for (j = y+1; j < y+h; j++) {
    for (i = x+1; i < x+w; i++) {
      screen[i][j] = (isprint(block) ? block : '?');
    }
  }
}

int One_Step(char *p, int c, int dir) {
  int i, j, success;
  success = FALSE;
  if (c != '.') for (j = 0; j < 6; j++) {   // top to bottom
    for (i = 0; i < 6; i++) { // left to right
      if (p[j*6+i] == c) { // first one it hits, topmost or leftmost
        
        if ((i < 4) && (p[j*6+i+2] == c)) {
          // left of horizontal triple
          //fprintf(stderr, "[3] left/right\n");
          if ((dir==LEFT) && (i > 0) && (p[j*6+i-1] == '.')) {
            // horizontal triple move left
            //fprintf(stderr, "[3] left\n");
            p[j*6+i-1] = c; p[j*6+i+2] = '.';
            success = TRUE;
          } else if ((dir==RIGHT) && (i < 3) && (p[j*6+i+3] == '.')) {
            // horizontal triple move right
            //fprintf(stderr, "[3] right\n");
            p[j*6+i+3] = c; p[j*6+i] = '.';
            success = TRUE;
          }
        } else if ((i < 5) && (p[j*6+i+1] == c)) {
          // left of horizontal pair
          //fprintf(stderr, "[2] left/right\n");
          if ((dir==LEFT) && (i > 0) && (p[j*6+i-1] == '.')) {
            // horizontal pair move left
            //fprintf(stderr, "[2] left\n");
            p[j*6+i-1] = c; p[j*6+i+1] = '.';
            success = TRUE;
          } else if ((dir==RIGHT) && (i < 4) && (p[j*6+i+2] == '.')) {
            // horizontal pair move right
            //fprintf(stderr, "[2] right\n");
            p[j*6+i+2] = c; p[j*6+i] = '.';
            success = TRUE;
          }
        }

        if ((j < 4) && (p[(j+2)*6+i] == c)) {
          // top of vertical triple
          //fprintf(stderr, "[3] up/down\n");
          if ((dir==UP) && (j > 0) && (p[(j-1)*6+i] == '.')) {
            // vertical triple move up
            p[(j-1)*6+i] = c; p[(j+2)*6+i] = '.';
            //fprintf(stderr, "[3] up\n");
            success = TRUE;
          } else if ((dir==DOWN) && (j < 3) && (p[(j+3)*6+i] == '.')) {
            // vertical triple down
            p[(j+3)*6+i] = c; p[j*6+i] = '.'; 
            //fprintf(stderr, "[3] down\n");
            success = TRUE;
         }
        } else if ((j < 5) && (p[(j+1)*6+i] == c)) {
          // top of vertical pair
          //fprintf(stderr, "[2] up/down\n");
          if ((dir==UP) && (j > 0) && (p[(j-1)*6+i] == '.')) {
            // vertical pair move up
            p[(j-1)*6+i] = c; p[(j+1)*6+i] = '.';
            //fprintf(stderr, "[2] up\n");
            success = TRUE;
          } else if ((dir==DOWN) && (j < 4) && (p[(j+2)*6+i] == '.')) {
            // vertical pair down
            p[(j+2)*6+i] = c; p[j*6+i] = '.';
            //fprintf(stderr, "[2] down\n");
            success = TRUE;
          }
        }

        return success;
      }
    }
  }
  return success;
}

void draw_screen(char *p) {
  int i,j;
  char block;

  for (j = 0; j < 18+1; j++) {
    for (i = 0; i < 36+1; i++) {
      if (j%3 == 0) {
        // +-----+------
        if (i%6 == 0) {
          screen[i][j] = '+';
        } else {
          screen[i][j] = '-';
        }
      } else {
        // |     |     |
        if (i%6 == 0) {
          screen[i][j] = ((j/3 == 2) && (i == 6*6) ? ' ' : '|');
        } else {
          screen[i][j] = ' ';
        }
      }
    }
  }
  for (j = 0; j < 6; j++) {   // top to bottom
    for (i = 0; i < 6; i++) { // left to right
      block = p[j*6+i];
      if (block == '.') continue;
      if ((i > 0) && (p[j*6+i-1] == block)) {
        // horizontal block handled already
      } else if ((i < 5) && (p[j*6+i+1] == block)) {
        // horizontal block
        draw_block(p, i,j, TRUE, block);
      } else if ((j > 0) && (p[(j-1)*6+i] == block)) {
        // vertical block handled already
      } else if ((j < 5) && (p[(j+1)*6+i] == block)) {
        // vertical block
        draw_block(p, i,j, FALSE, block);
      } else {
        // error
      }
    }
  }

  for (j = 0; j < 18+1; j++) {
    for (i = 0; i < 36+1; i++) {
      if (isprint(screen[i][j])) {
        fputc(screen[i][j], stdout);
      } else {
        fputc('!', stdout);
      }
    }
    fputc('\n', stdout);
  }
}

static inline int solved(char *p) {
  return ((p[2*6+4] == 'X') && (p[2*6+5] == 'X'));
}

static void show_results(char *orig, char *moves) {
  int block, dir;
  for (;;) {
    if (*moves == '\0') {
      printf("\nSolution:\n");
      draw_screen(orig);
      return;
    }
    printf("\nBefore applying %c:\n", *moves);
    draw_screen(orig);
    sleep(1);
    block = *moves++;
    if (block == '\0') break;
    dir = 1;
    if (islower(block)) {
      dir = 0;
      block = toupper(block);
    }
    One_Step(orig, block, dir);
  }
}

int main(int argc, char **argv) {
  int steps;
  char p[BOARD_SIZE];
  char orig[BOARD_SIZE];

  if ( (argc != 2) && (argc != 3) ) {
    fprintf(stderr, "syntax: solve \"....................................\" [optional solution]\n\n");
    exit(1);
  }

  strcpy(p, argv[1]);
  if (strlen(p) != 36) {
    fprintf(stderr, "solve: parameter must be 36 characters\n\n");
    exit(2);
  }

  strcpy(orig, p);
  if (argc == 3) {
    int c;
    char *s = argv[2];
    draw_screen(orig);
    sleep(2);
    while ((c = *s++) != '\0') {
      int dir = RIGHT;
      if (!isalpha(c)) {
        fprintf(stderr, "solve: solution string must be all alphabetic! '%s'\n\n", argv[2]);
        exit(3);
      }
      if (islower(c)) {
        dir = LEFT;
        c = toupper(c);
      }
      One_Step(orig, c, dir);
      sleep(2);
      printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nExecute '%c':\n", s[-1]);
      draw_screen(orig);
    }
  } else {
    if (solvable(p, &steps)) {
      strcpy(orig, argv[1]);
      show_results(orig, &p[NUM_ROWS*NUM_COLS+1]);
      printf("%d Moves. \"%s\" \"%s\"\n", steps, argv[1], &p[NUM_ROWS*NUM_COLS+1]);
      //printf("%d Moves. \"%s\"\n", steps, p);
    } else {
      fprintf(stderr, "solve: not solvable\n\n");
      exit(0);
    }
  }
  
  return 0;
}
