/****************************************************************************
 * apps/system/sudoku/sudoku.c
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "system/readline.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define NHISTORY 16

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct sudoku_s
{
  uint16_t canbe;
  uint16_t is;
};
typedef struct sudoku_s sudoku_t[81];

/****************************************************************************
 * Private Data
 ****************************************************************************/

static sudoku_t history[NHISTORY];
static sudoku_t working;
static int first;
static int last;
static int current;

static const char *r1[8] =
  {"   ", "1  ", " 2 ", "12 ", "  3", "1 3", " 23", "123" };
static const char *r2[8] =
  {"   ", "4  ", " 5 ", "45 ", "  6", "4 6", " 56", "456" };
static const char *r3[8] =
  {"   ", "7  ", " 8 ", "78 ", "  9", "7 9", " 89", "789" };
static char line[256];
static int nis = 0;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static inline void init_array(void)
{
  int i;

  first = 0;
  last = 0;
  current = 0;

  for (i = 0; i < 81; i++)
    {
      working[i].canbe = 0x1ff;
      working[i].is    = 0x000;
    }
}

static inline void push(void)
{
  int next = current + 1;
  if (next >= NHISTORY) next = 0;

  if (current == last)
    {
      last = next;
    }

  if (first == next)
  {
    if (++first >= NHISTORY) first = 0;
  }

  memcpy (history[current], working, sizeof(sudoku_t));
  current = next;
}

static inline void undo(void)
{
  if (current == first)
    {
      printf("Cannot undo\n");
    }
  else
    {
      if (--current < 0) current = NHISTORY-1;
      memcpy (working, history[current], sizeof(sudoku_t));
    }
}

static inline void redo(void)
{
  if (current == last)
    {
      printf("Cannot redo\n");
    }
  else
    {
      if (++current >= NHISTORY) current = 0;
      memcpy(working, history[current], sizeof(sudoku_t));
    }
}

static inline void show_row(int row)
{
  printf("    |%3s|%3s|%3s| |%3s|%3s|%3s| |%3s|%3s|%3s|\n",
         r1[working[9*row + 0].canbe & 0x007],
         r1[working[9*row + 1].canbe & 0x007],
         r1[working[9*row + 2].canbe & 0x007],
         r1[working[9*row + 3].canbe & 0x007],
         r1[working[9*row + 4].canbe & 0x007],
         r1[working[9*row + 5].canbe & 0x007],
         r1[working[9*row + 6].canbe & 0x007],
         r1[working[9*row + 7].canbe & 0x007],
         r1[working[9*row + 8].canbe & 0x007]);
  printf(" %2d |%3s|%3s|%3s| |%3s|%3s|%3s| |%3s|%3s|%3s|\n", row + 1,
         r2[(working[9*row + 0].canbe >> 3) & 0x007],
         r2[(working[9*row + 1].canbe >> 3) & 0x007],
         r2[(working[9*row + 2].canbe >> 3) & 0x007],
         r2[(working[9*row + 3].canbe >> 3) & 0x007],
         r2[(working[9*row + 4].canbe >> 3) & 0x007],
         r2[(working[9*row + 5].canbe >> 3) & 0x007],
         r2[(working[9*row + 6].canbe >> 3) & 0x007],
         r2[(working[9*row + 7].canbe >> 3) & 0x007],
         r2[(working[9*row + 8].canbe >> 3) & 0x007]);
  printf("    |%3s|%3s|%3s| |%3s|%3s|%3s| |%3s|%3s|%3s|\n",
         r3[(working[9*row + 0].canbe >> 6) & 0x007],
         r3[(working[9*row + 1].canbe >> 6) & 0x007],
         r3[(working[9*row + 2].canbe >> 6) & 0x007],
         r3[(working[9*row + 3].canbe >> 6) & 0x007],
         r3[(working[9*row + 4].canbe >> 6) & 0x007],
         r3[(working[9*row + 5].canbe >> 6) & 0x007],
         r3[(working[9*row + 6].canbe >> 6) & 0x007],
         r3[(working[9*row + 7].canbe >> 6) & 0x007],
         r3[(working[9*row + 8].canbe >> 6) & 0x007]);
}

static inline void show_array(void)
{
  int row;
  int i;

  printf("    +===+===+===+ +===+===+===+ +===+===+===+\n");
  printf("    | 1 | 2 | 3 | | 4 | 5 | 6 | | 7 | 8 | 9 |\n");
  printf("    +===+===+===+ +===+===+===+ +===+===+===+\n");

  row = 0;
  for (i = 0; i < 3; i++)
    {
      show_row(row++);
      printf("    +---+---+---+ +---+---+---+ +---+---+---+\n");
      show_row(row++);
      printf("    +---+---+---+ +---+---+---+ +---+---+---+\n");
      show_row(row++);
      printf("    +===+===+===+ +===+===+===+ +===+===+===+\n");
    }
}

static inline int is_unique(unsigned int val)
{
  switch (val)
    {
    case 0x001:
    case 0x002:
    case 0x004:
    case 0x008:
    case 0x010:
    case 0x020:
    case 0x040:
    case 0x080:
    case 0x100:
      return 1;
    default:
      return 0;
    }
}

static inline int impossible(void)
{
  int nchanges = 0;
  unsigned int val;
  int row1, col1;
  int row2, col2;
  int i, j;
  int ndx1, ndx2;

  for (row1 = 0; row1 < 9; row1++)
    {
      row2 = row1 / 3;
      for (col1 = 0; col1 < 9; col1++)
        {
          col2 = col1 / 3;
          ndx1 = row1*9 + col1;
          val = working[ndx1].canbe;

          for (i = 0; i < 9; i++)
            {
              ndx2 = i*9 + col1;
              if (ndx1 != ndx2)
                {
                  val &= ~working[ndx2].is;
                }
            }

          for (j = 0; j < 9; j++)
            {
              ndx2 = row1*9 + j;
              if (ndx1 != ndx2)
                {
                  val &= ~working[ndx2].is;
                }
            }

          for (i = 0; i < 3; i++)
            {
              for (j = 0; j < 3; j++)
                {
                  ndx2 = (row2*3 + i)*9 + (col2*3 + j);
                  if (ndx1 != ndx2)
                    {
                      val &= ~working[ndx2].is;
                    }
                }
            }

          if (val == 0)
            {
              return -1;
            }

          if (val != working[ndx1].canbe)
            {
              nchanges++;
              working[ndx1].canbe = val;

              if (is_unique(val))
                {
                  working[ndx1].is = val;
                }
            }
        }
    }

  return nchanges;
}

static inline int unique(void)
{
  int noccurences[9];
  int lastndx[9];
  int nchanges = 0;
  unsigned int mask;
  unsigned int val;
  int row1, col1;
  int row2, col2;
  int ndx;
  int i;

  for (row1 = 0; row1 < 9; row1++)
    {
      for (i = 0; i < 9; i++)
        {
          noccurences[i] = 0;
          lastndx[i] = -1;
        }

      for (i = 0; i < 9; i++)
        {
          mask = 1 << i;
          for (col1 = 0; col1 < 9; col1++)
            {
              ndx = row1 * 9 + col1;
              if (working[ndx].canbe & mask)
                {
                  noccurences[i]++;
                  lastndx[i] = ndx;
                }
            }

          for (i = 0; i < 9; i++)
            {
              if (noccurences[i] == 1)
                {
                  val = 1 << i;
                  ndx = lastndx[i];
                  if (working[ndx].is != val)
                    {
                      working[ndx].is = val;
                      working[ndx].canbe = val;
                      nchanges++;
                    }
                }
            }
        }
    }

  for (col1 = 0; col1 < 9; col1++)
    {
      for (i = 0; i < 9; i++)
        {
          noccurences[i] = 0;
          lastndx[i] = -1;
        }

      for (i = 0; i < 9; i++)
        {
          mask = 1 << i;
          for (row1 = 0; row1 < 9; row1++)
            {
              ndx = row1 * 9 + col1;
              if (working[ndx].canbe & mask)
                {
                  noccurences[i]++;
                  lastndx[i] = ndx;
                }
            }

          for (i = 0; i < 9; i++)
            {
              if (noccurences[i] == 1)
                {
                  val = 1 << i;
                  ndx = lastndx[i];
                  if (working[ndx].is != val)
                    {
                      working[ndx].is = val;
                      working[ndx].canbe = val;
                      nchanges++;
                    }
                }
            }
        }
    }

  for (row2 = 0; row2 < 3; row2++)
    {
      for (col2 = 0; col2 < 3; col2++)
        {
          for (i = 0; i < 9; i++)
            {
              noccurences[i] = 0;
              lastndx[i] = -1;
            }

          for (i = 0; i < 9; i++)
            {
              mask = 1 << i;
              for ( row1 = 0; row1 < 3; row1++)
                {
                  for ( col1 = 0; col1 < 3; col1++)
                    {
                      ndx = (row2*3 + row1)*9 + (col2*3 + col1);
                      if (working[ndx].canbe & mask)
                        {
                          noccurences[i]++;
                          lastndx[i] = ndx;
                        }
                    }
                }
            }

          for (i = 0; i < 9; i++)
            {
              if (noccurences[i] == 1)
                {
                  val = 1 << i;
                  ndx = lastndx[i];
                  if (working[ndx].is != val)
                    {
                      working[ndx].is = val;
                      working[ndx].canbe = val;
                      nchanges++;
                    }
                }
            }
        }
    }

  return nchanges;
}

static inline int getcell(int *pndx, int *pval)
{
  int row, col, ndx;
  unsigned int val;
  int i = 0;

  while ((line[i] == ' ' || line[i] == '\t') && (line[i] != '\0') && (line[i] != '\n')) i++;

  if (line[i] < '1' || line[i] > '9')
    {
      printf("Bad row: %c\n", line[i]);
      return -1;
    }
  row = line[i++] -'0' - 1;

  while ((line[i] == ' ' || line[i] == '\t') && (line[i] != '\0') && (line[i] != '\n')) i++;

  if (line[i] < '1' || line[i] > '9')
    {
      printf("Bad col: %c\n", line[i]);
      return -1;
    }
  col = line[i++] -'0' - 1;
  ndx = row*9 + col;

  while ((line[i] == ' ' || line[i] == '\t') && (line[i] != '\0') && (line[i] != '\n')) i++;

  if (line[i] < '1' || line[i] > '9')
    {
      printf("Bad value: %c\n", line[i]);
      return -1;
    }

  val = 1 << ((line[i] - '0') - 1);
  if (( val & working[ndx].canbe) == 0)
    {
      printf("Impossible value: %c\n", line[i]);
      return -1;
    }

  *pndx = ndx;
  *pval = val;
  return 0;
}

static inline int get_command(void)
{
  printf("%d of 81> ", nis);
  fflush(stdout);
  (void)readline(line, 256, stdin, stdout);

  int i = 0;

  while ((line[i] == ' ' || line[i] == '\t') && (line[i] != '\0') && (line[i] != '\n')) i++;

  return line[i];
}

static inline void show_usage(void)
{
  printf("ABC - Set cell[A,B] to C where A,B, and C are in {1..9}\n");
  printf("U   - Undo last cell assignment\n");
  printf("R   - Redo last cell assignment\n");
  printf("H|? - Show this message\n");
  printf("Q   - Quit\n");
}

static inline void count_cells(void)
{
  int i;

  nis = 0;
  for (i = 0; i < 81; i++) if (working[i].is > 0) nis++;
}

/****************************************************************************
 * Private Functions
 ****************************************************************************/

#ifdef BUILD_MODULE
int main(int argc, FAR char *argv[])
#else
int sudoku_main(int argc, char **argv, char **envp)
#endif
{
  int cmd;
  int nchanged;
  int val;
  int ndx;

  init_array();
  for (;;)
    {
      count_cells();
      show_array();
      if (nis == 81)
        {
          printf("GAME OVER: 81 of 81 assigned\n");
          return 0;
        }
      else
        {
          cmd = get_command();
          switch (cmd)
            {
            case 'U':
            case 'u':
              undo();
              break;

            case 'R':
            case 'r':
              redo();
              break;

            case 'H':
            case 'h':
            case '?':
              show_usage();
              break;

            case 'Q':
            case 'q':
              return 0;

            default:
              if (getcell(&ndx, &val) == 0)
                {
                  push();
                  working[ndx].is    = val;
                  working[ndx].canbe = val;
                  for (;;)
                    {
                      nchanged = impossible();
                      if (nchanged < 0)
                        {
                          printf("This move is not possible\n");
                          undo();
                          break;
                        }
                      else if (nchanged == 0)
                        {
                          nchanged = unique();
                          if (nchanged == 0)
                            {
                              break;
                            }
                        }
                    }
                }
            }
        }
    }
}