#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include "board.h"


/**
 * ANSI control codes
 */
#define COLOUR_RED    "\e[31m"
#define COLOUR_RESET  "\e[0m"
#define CLEAR         "\033[2J"


/**
 * How many boards to allocate by default for board spec
 * Average depth for highly complex boards is between 10 and 12
 */
#define DEFAULT_DEPTH 10

/**
 * How many boards to allocate per depth increase
 * Higher values mean fewer reallocations, by with greater resource costs
 */
#define DEPTH_INCREMENT 3



struct args {
  bool valid;
  unsigned verbosity : 2;
  char *file_name;
};


struct boards_table {
  struct board **board_specs;
  unsigned long long max_depth;
};


void
tables_ensure_depth (struct boards_table *board_spec, unsigned long long depth)
{
  if (board_spec->max_depth <= depth)
  {
    /* Compute new max depth */
    unsigned long long new_max;
    if (board_spec->max_depth == 0)
      new_max = DEFAULT_DEPTH;
    else
      new_max = board_spec->max_depth + DEPTH_INCREMENT;

    /* Disregard NULL return value. What's it gonna do, segfault? :P */
    board_spec->board_specs = realloc (
        board_spec->board_specs,
        sizeof (struct board *) * new_max
    );

    /* Allocate boards */
    for (unsigned long long l = board_spec->max_depth; l < new_max; ++l)
      board_spec->board_specs[l] = malloc (sizeof (struct board));

    /* Update max depth */
    board_spec->max_depth = new_max;
  }
}

bool
simplify (
  struct boards_table *board_spec,
  unsigned long long depth,
  unsigned long long *counter,
  unsigned verbosity
);


struct board_file
{
  int fd;
  void *data;
};


/**
 * Determines if the given char is a valid character for defining
 * a board element
 */
static inline bool
is_valid_def (char def)
{
  return  def == ' '    ||
          (
            def >= '0'  &&
            def <= '9'
          );
}

/**
 * Load a board-definition file and return file descriptor
 */
static struct board_file
load_board_file (const char *path)
{
  struct board_file file;
  file.fd = -1;
  file.data = NULL;

  /* Open file */
  int fd = open (path, 0);
  if (fd < 0)
    return file;

  /* Map 89 bytes of file to memory */
  void *region = mmap (NULL, 89, PROT_READ, MAP_SHARED, fd, 0);
  if (region == (void*)-1)
  {
    close (fd);
    return file;
  }

  /*
    Ensure data in file is formatted correctly:
      1) Every 10th character is ignored (row-terminator)
      2) Each character must be in range '0'-'9' or ' '
  */
  char *data = region;
  for (unsigned i = 1; i <= 89; ++i)
    if (!((i % 10) == 0) && !is_valid_def (data[i - 1]))
    {
      munmap (region, 89);
      close (fd);
      return file;
    }

  /* Save file data to struct and return */
  file.fd = fd;
  file.data = region;

  return file;
}

/**
 * Free all resources linked to an opened board definition file
 */
static void
close_board_file (struct board_file file)
{
  close (file.fd);
  munmap (file.data, 89);
}

static void
copy_to_board (struct board_file file, struct board *board)
{
  // Clear board
  board_init (board);

  char *data = file.data;
  for (unsigned i = 1; i <= 89; ++i)
    if ((i % 10) != 0)
    {
      board_pos x = i % 10;
      board_pos y = i / 10;

      if (data[i - 1] != ' ')
        board_place (board, x - 1, y, data[i - 1] - '0' - 1);
    }
}


static void
ansi_set_cursor (unsigned y, unsigned x)
{
  printf ("\033[%u;%uH", x + 1, y + 1);
}


static void
ansi_clear_screen ()
{
  puts (CLEAR);
}


void
ansi_cursor_show (bool show)
{
  if (show)
    fputs("\e[?25h", stdout);
  else
    fputs("\e[?25l", stdout);
}

static void
print_board_verbose (
  const struct board *board,
  unsigned whence_x,
  unsigned whence_y
)
{
  for (board_pos y = 0; y < 9; ++y)
  {
    for (board_pos x = 0; x < 9; ++x)
    {
      for (element_value vy = 0; vy < 3; ++vy)
        for (element_value vx = 0; vx < 3; ++vx)
        {
          element_value check = vx + (vy * 3);

          ansi_set_cursor (whence_x + (x * 4) + vx, whence_y + (y * 4) + vy);
          
          if (board_has_value (board, x, y))
            printf ("%u", board_get_value (board, x, y) + 1);
          else if (board_is_marked (board, x, y, check))
            printf (COLOUR_RED "%u" COLOUR_RESET, check + 1);
          else
            fputs (" ", stdout);

          if (vx == 2 && x != 8)
            fputs ("|", stdout);
        }
    }
    
    ansi_set_cursor (0, (y * 4) + 3);
    if (y != 8)
    {
      for (unsigned i = 0; i < (4 * 9) - 1; ++i)
        if ((i + 1) % 4 == 0)
          fputs ("+", stdout);
        else
          fputs ("-", stdout);
    }
  }

  fflush (stdout);
}


static void
print_board (const struct board *board, const struct board *compare, unsigned whence_x, unsigned whence_y)
{
  for (board_pos y = 0; y < 9; ++y)
  {
    /* Print row */
    for (board_pos x = 0; x < 9; ++x)
    {
      ansi_set_cursor (whence_x + (x * 2), whence_y + (y * 2));

      /* Print board element */
      if (board_has_value (board, x, y))
      {
        if (compare != NULL && ! board_has_value (compare, x, y))
          printf (COLOUR_RED "%u" COLOUR_RESET, board_get_value (board, x, y) + 1);
        else
          printf ("%u", board_get_value (board, x, y) + 1);
      }
      else
        fputs (" ", stdout);

      /* Print column element delimiter */
      if (x < 8)
        fputs("|", stdout);
    }
    
    /* Print row element delimiter */
    if (y < 8)
    {
      for (board_pos x = 0; x < 17; ++x)
      {
        ansi_set_cursor (whence_x + x, whence_y + (y * 2 + 1));
        if ((x & 1) == 0)
          fputs ("-", stdout);
        else
          fputs ("+", stdout);
      }
    }
  }
}


/**
 * Compute first potential value of a given element
 */
element_value
first_potential_value (struct board_element *element, struct board *board, bool *error)
{
  unsigned short potential = element->potential;
  if (potential == 0)
  {
    *error = true;
    return 0;
  }

  *error = false;
  
  element_value value = 0;
  while (potential != 0)
  {
    if ((potential & 1) == 1)
      return value;
    ++value;
    potential >>= 1;
  }
  
  /* Unreachable */
  abort();
}


/**
 * Reduce away all elements on board with complexity=1 until none remain
 */
bool
simplify (
  struct boards_table *board_specs,
  unsigned long long depth,
  unsigned long long *counter,
  unsigned verbosity
)
{
  /* Get current table */
  struct board *board = board_specs->board_specs[depth];

  if (verbosity > 0)
  {
    if (((*counter) & (0xFFFF >> (4 * (4 - verbosity)))) == 0)
    {
      print_board_verbose (board, 0, 0);
      ansi_set_cursor (0, 35);
      printf ("Iteration: %llu", *counter);
    }
    *counter += 1;
  }


  bool error;

  unsigned count = 0;

  /* Reduce using low-complexity computation */
  while (board->complexity == 1)
  {
    for (board_pos y = 0; y < 9; ++y)
      for (board_pos x = 0; x < 9; ++x)
        if (! board_has_value (board, x, y))
        {
          struct board_element *elem = BOARD_ELEM (board, x, y);
          if (elem->complexity == 1)
          {
            element_value value = first_potential_value (elem, board, &error);
            if (error) return false;

            ++count;

            //fprintf (stderr, "Placing (%u, %u)=%u (potential=%u)\n", x, y, value, elem->potential);
            
            if (! board_place (board, x, y, value))
            {
              bool rerr = meta_has_value (BOARD_ROW (board, y), value);
              bool cerr = meta_has_value (BOARD_COL (board, x), value);
              bool qerr = meta_has_value (BOARD_QUAD (board, TO_QUAD (x), TO_QUAD (y)), value);
              fprintf (stderr, "Error (%u, %u, %u)\n", rerr, cerr, qerr);
            }
          }
        }

    board_refresh_complexity (board);
  }

  /* Attempt to reduce with speculative placement */
  if (board->complexity > 1)
  {
    for (board_pos y = 0; y < 9; ++y)
      for (board_pos x = 0; x < 9; ++x)
      {
        struct board_element *elem = BOARD_ELEM (board, x, y);
        /* Find a simplest element on the board */
        if (
            ! board_has_value (board, x, y) &&
            elem->complexity == board->complexity
        )
          for (element_value value = 0; value < 9; ++value)
          {
            /* Try speculative placement of each potential value and recurse */
            tables_ensure_depth (board_specs, depth + 1);
            if ((elem->potential & (1 << value)) != 0)
            {
              struct board *board_spec =
                board_place_speculative (
                  board,
                  board_specs->board_specs[depth + 1],
                  x,
                  y,
                  value
                );

              /* If speculative placement failed, try another value */
              if (board_spec == NULL)
              {
                board_unmark (board, x, y, value);
                continue;
              }

              /* Found solution */
              if (
                  simplify (
                    board_specs,
                    depth + 1,
                    counter,
                    verbosity
                  ) &&
                  board_spec->complexity == 0)
              {
                board_copy (board_spec, board);
                x = 9;
                y = 9;
                value = 9;
              }
              else board_unmark (board, x, y, value);
            }
          }
      }
  }
  return true;
}


struct args
argparse (int argc, char **argv)
{
  struct args result;
  result.file_name = NULL;
  result.valid = true;
  result.verbosity = 0;
  if (argc < 2)
  {
    result.valid = false;
    return result;
  }

  for (int i = 1; i < argc; ++i)
    if (strncmp (argv[i], "-", 1) == 0)
    {
      if (result.verbosity != 0)
      {
        result.valid = false;
        return result;
      }
      if (strcmp (argv[i], "-v") == 0)
        result.verbosity = 1;
      else if (strcmp (argv[i], "-vv") == 0)
        result.verbosity = 2;
      else
      {
        result.valid = false;
        return result;
      }
    }
    else if (result.file_name == NULL)
      result.file_name = argv[i];
    else
    {
      result.valid = false;
      return result;
    }
  return result;
}



int
main (int argc, char **argv, char **env)
{
  struct args args = argparse (argc, argv);
  if (! args.valid)
  {
    fputs ("Badly formatted arguments! Usage:\n\t./sudoku [-v[v]] {file name}\n", stderr);
    return 1;
  }
  
  struct board_file file = load_board_file (args.file_name);
  if (file.fd == -1 || file.data == NULL)
    return -1;

  ansi_cursor_show (false);

  /* Allocate boards */
  struct board original;

  struct boards_table boards;
  boards.max_depth = 0;
  boards.board_specs = NULL;
  tables_ensure_depth (&boards, 0);

  struct board *root_board = boards.board_specs[0];

  copy_to_board (file, &original);
  board_copy (&original, boards.board_specs[0]);

  close_board_file (file);



  ansi_clear_screen ();
  
  if (! board_is_valid (root_board))
  {
    fputs ("Supplied board is not valid!\n", stderr);

    ansi_cursor_show (true);
    
    return 1;
  }
  

  if (args.verbosity == 0)
    puts ("Simplifying...");


  board_refresh_complexity (root_board);

  /* Profiler start time */
  clock_t start_clk = clock ();

  unsigned long long counter = 0;
  
  simplify (&boards, 0, &counter, args.verbosity);

  /* Profiler end time */
  clock_t end_clk = clock ();

  ansi_clear_screen ();

  if (root_board->complexity == 0)
  {
    print_board (&original, NULL, 0, 0);
    print_board (root_board, &original, 21, 0);
    ansi_set_cursor (0, 18);
  }
  else
  {
    print_board_verbose (root_board, 0, 0);
    ansi_set_cursor (0, 36);
  }
  printf ("Simplification took %Lf seconds\n", ((long double)(end_clk - start_clk))/CLOCKS_PER_SEC);


  ansi_cursor_show (true);

  return 0;
}