//  Match3 Board - Version 1.0
//  Created: 28/10/2017 11:46:51
//  John Ryland (jryland@xiaofrog.com)
//  InvertedLogic, (C) Copyright 2017
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include "board.h"


// Some C++11 compatibility
#ifndef __CPP11
#  define nullptr   NULL
#endif


bool createBoard(Board& a_board, int a_width, int a_height)
{
  const size_t allocatedBytes = a_width*a_height*sizeof(int);
  a_board.m_width = a_width;
  a_board.m_height = a_height;
  a_board.m_board = new int[allocatedBytes];
  if (!a_board.m_board)
    return false;
  memset(a_board.m_board, 0, allocatedBytes);
  return true;
}

bool generateRandomBoard(Board& a_board, int a_width, int a_height, int a_numTileTypes)
{
  if (!createBoard(a_board, a_width, a_height))
    return false;
  for (int j = 0; j < a_height; j++)
    for (int i = 0; i < a_width; i++)
      a_board.m_board[j*a_width + i] = rand() % a_numTileTypes;
  return true;
}

bool generateBoardWithNoMatch3s(Board& a_board, int a_width, int a_height, int a_numTileTypes)
{
  int iter = 0;
  while (iter < 100)
  {
    iter++;
    if (!generateRandomBoard(a_board, a_width, a_height, a_numTileTypes))
      continue;
    if (!hasAMatch3(a_board)) {
      printf("iterated %i times in generateBoardWithNoMatch3s\n", iter);
      return true;
    }
    destroyBoard(a_board);
  }
  return false;
}

bool generatePlayableBoard(Board& a_board, int a_width, int a_height, int a_numTileTypes)
{
  int iter = 0;
  while (iter < 100)
  {
    iter++;
    if (!generateBoardWithNoMatch3s(a_board, a_width, a_height, a_numTileTypes))
      continue;
    if (hasMoveAvailable(a_board)) {
      printf("iterated %i times in generatePlayableBoard\n", iter);
      return true;
    }
    destroyBoard(a_board);
  }
  return false;
}

void destroyBoard(Board& a_board)
{
  delete[] a_board.m_board;
}

void printBoard(const Board& a_board)
{
  for (int i = 0; i < a_board.m_width+2; i++)
    printf("-");
  printf("\n");
  for (int j = 0; j < a_board.m_height; j++)
  {
    printf("|");
    for (int i = 0; i < a_board.m_width; i++)
    {
      printf("%i", a_board.m_board[j*a_board.m_width + i]);
    }
    printf("|\n");
  }
  for (int i = 0; i < a_board.m_width+2; i++)
    printf("-");
  printf("\n");
}

bool getMatch3(const Board& a_board, int& x1, int& y1, int& x2, int& y2, int& x3, int& y3)
{
  for (int j = 0; j < a_board.m_height; j++)
  {
    int countSame = 0;
    int prev = -1;
    for (int i = 0; i < a_board.m_width; i++)
    {
      int cur = a_board.m_board[j*a_board.m_width + i];
      if (prev == cur) {
        countSame++;
        if (countSame >= 3) {
          x1 = i - 2; x2 = i - 1; x3 = i;
          y1 = y2 = y3 = j;
          return true;
        }
      } else {
        countSame = 1;
        prev = cur;
      }
    }
  }

  for (int i = 0; i < a_board.m_width; i++)
  {
    int countSame = 0;
    int prev = -1;
    for (int j = 0; j < a_board.m_height; j++)
    {
      int cur = a_board.m_board[j*a_board.m_width + i];
      if (prev == cur) {
        countSame++;
        if (countSame >= 3) {
          x1 = x2 = x3 = i;
          y1 = j - 2; y2 = j - 1; y3 = j;
          return true;
        }
      } else {
        countSame = 1;
        prev = cur;
      }
    }
  }

  return false;
}

bool hasAMatch3(const Board& a_board)
{
  int x1, y1, x2, y2, x3, y3;
  return getMatch3(a_board, x1, y1, x2, y2, x3, y3);
}

bool trySwapCheck(int x1, int y1, int x2, int y2, const Board& a_board)
{
  int w = a_board.m_width;
  int h = a_board.m_height;
  // some pre-condition checking
  if (x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0 || x1 >= w || y1 >= h || x2 >= w || y2 >= h)
  {
    printf("trySwap called with tile position out of range: %i,%i -> %i,%i  dims: %i,%i\n",
      x1, y1, x2, y2, w, h);
    return false;
  }
  return true;
}

int trySwapH(int x1, int y1, int x2, int y2, const Board& a_board)
{
  if (y1 != y2) { printf("non-horizontal swap %i %i\n", y1, y2); return 0; }
  if (!trySwapCheck(x1, y1, x2, y2, a_board)) return 0;
  int _x1 = std::min(x1,x2);
  int _x2 = std::max(x1,x2);
  if (_x2 - _x1 != 1) { printf("non-single line swap %i %i\n", x1, x2); return 0; }

/*
  // optimization so we just do a faster check
  // checkRange from  _x1 - 2 -> _x2 + 2   y1 - 2  ->  y1 + 2
  int cx1 = std::max(_x1 - 2, 0);
  int cx2 = std::min(_x2 + 2, a_board.m_width - 1);
  int cy1 = std::max(y1 - 2, 0);
  int cy2 = std::min(y1 + 2, a_board.m_height - 1);
*/

  int tmp = a_board.m_board[y1*a_board.m_width + _x1];
  a_board.m_board[y1*a_board.m_width + _x1] = a_board.m_board[y1*a_board.m_width + _x2];
  a_board.m_board[y1*a_board.m_width + _x2] = tmp;
  bool ret = hasAMatch3(a_board);
  tmp = a_board.m_board[y1*a_board.m_width + _x1];
  a_board.m_board[y1*a_board.m_width + _x1] = a_board.m_board[y1*a_board.m_width + _x2];
  a_board.m_board[y1*a_board.m_width + _x2] = tmp;
  return (ret) ? 1 : 0;
}

int trySwapV(int x1, int y1, int x2, int y2, const Board& a_board)
{
  if (x1 != x2) { printf("non-vertical swap %i %i\n", x1, x2); return 0; }
  if (!trySwapCheck(x1, y1, x2, y2, a_board)) return 0;
  int _y1 = std::min(y1,y2);
  int _y2 = std::max(y1,y2);
  if (_y2 - _y1 != 1) { printf("non-single line swap %i %i\n", y1, y2); return 0; }

  int tmp = a_board.m_board[_y1*a_board.m_width + x1];
  a_board.m_board[_y1*a_board.m_width + x1] = a_board.m_board[_y2*a_board.m_width + x1];
  a_board.m_board[_y2*a_board.m_width + x1] = tmp;
  bool ret = hasAMatch3(a_board);
  tmp = a_board.m_board[_y1*a_board.m_width + x1];
  a_board.m_board[_y1*a_board.m_width + x1] = a_board.m_board[_y2*a_board.m_width + x1];
  a_board.m_board[_y2*a_board.m_width + x1] = tmp;
  return (ret) ? 1 : 0;
}

bool hasMoveAvailable(const Board& a_board)
{
  int moves = 0;
  for (int j = 0; j < a_board.m_height/2; j++)
  {
    for (int i = 0; i < a_board.m_width/2; i++)
    {
      moves += trySwapH(  i,j, i+1,j, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
      moves += trySwapV(  i,j, i,j+1, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
    }
    for (int i = a_board.m_width/2; i < a_board.m_width; i++)
    {
      moves += trySwapH(  i,j, i-1,j, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
      moves += trySwapV(  i,j, i,j+1, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
    }
    if (moves)
      return true;
  }
  for (int j = a_board.m_height/2; j < a_board.m_height; j++)
  {
    for (int i = 0; i < a_board.m_width/2; i++)
    {
      moves += trySwapH(  i,j, i+1,j, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
      moves += trySwapV(  i,j, i,j-1, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
    }
    for (int i = a_board.m_width/2; i < a_board.m_width; i++)
    {
      moves += trySwapH(  i,j, i-1,j, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
      moves += trySwapV(  i,j, i,j-1, a_board);
      if (moves) { printf("move avail: %i %i\n", i, j); return true; }
    }
    if (moves)
      return true;
  }
  if (moves)
    return true;
  return false;
}

bool canSwapTiles(const Board& a_board, int x1, int y1, int x2, int y2)
{
  return trySwapH(x1, y1, x2, y2, a_board) || trySwapV(x1, y1, x2, y2, a_board);
}

