#include "Board.h"

#include <string>

namespace Battleship{

Board::Board(int rows, int cols, int* r_sums, int* c_sums, Cell* constraints){
    this->rows = new int(rows);
    this->cols = new int(cols);
    this->r_sums = r_sums;
    this->c_sums = c_sums;
    //This is just here for coding sanity
    r_sums = c_sums = NULL;
    this->constraints = new int(0);
    cellList = constraints;
    extraCells = NULL;

    this->r_occupied = new int[rows];
    this->c_occupied = new int[cols];
    for (int i = 0; i < rows; i++) r_occupied[i] = (this->r_sums[i] > cols);
    for (int i = 0; i < cols; i++) c_occupied[i] = (this->c_sums[i] > rows);

/*
    //Copy the occupied states over
    std::copy(r_occupied, r_occupied+cols, this->r_occupied);
    std::copy(c_occupied, c_occupied+rows, this->c_occupied);
*/

    //This should initialize the array as an array of NULLs
    state = new Cell**[rows];
    for (int i = 0; i < rows; i++){
        state[i] = new Cell*[cols]();
    }

    //Set the cells appropriately
    for (; constraints != NULL; constraints = constraints->getLinkedCell()){
        state[constraints->getY()][constraints->getX()] = constraints;
        if (constraints->getType() == 'o'){
            r_occupied[constraints->getY()]++;
            c_occupied[constraints->getX()]++;
        }
        else if (constraints->getType() != ' ')
            (*this->constraints)++;
    }

    childBoard = false;
    destroyed = false;
}

//Child board constructor (private)
Board::Board(const Board& parent, int start, uint8_t size, int row_col, bool horizontal){
    rows = parent.rows;
    cols = parent.cols;
    r_sums = parent.r_sums;
    c_sums = parent.c_sums;
    r_occupied = parent.r_occupied;
    c_occupied = parent.c_occupied;
    state = parent.state;
    constraints = parent.constraints;

    /*
    r_occupied = new int[rows];
    c_occupied = new int[cols];

    //Copy the occupied states over
    std::copy(parent.r_occupied, parent.r_occupied+cols, r_occupied);
    std::copy(parent.c_occupied, parent.c_occupied+rows, c_occupied);
    */
    //Occupy the cells!
    cellList = NULL;
    extraCells = NULL;
    //Submarine!
    if (size == 1){
        int x = (horizontal) ? start   : row_col;
        int y = (horizontal) ? row_col : start;
        state[y][x] = cellList = new Cell('o', x, y);
        r_occupied[y]++;
        c_occupied[x]++;
    }
    //Anything larger! Horizontal Case!
    else if (horizontal){
        if (!state[row_col][start]){
            state[row_col][start] = cellList = new Cell('<', start, row_col, cellList);
        } else{
            extraCells = new Cell('<', start, row_col, extraCells);
            (*constraints)--;
        }
        r_occupied[row_col]++;
        c_occupied[start]++;

        for (int i = start+1; i < start+size-1; i++){
            if (!state[row_col][i]){
                state[row_col][i] = cellList = new Cell('X', i, row_col, cellList);
            } else{
                extraCells = new Cell('X', i, row_col, extraCells);
                (*constraints)--;
            }
            r_occupied[row_col]++;
            c_occupied[i]++;
        }

        if (!state[row_col][start+size-1]){
            state[row_col][start+size-1] = cellList = new Cell('>', start+size-1, row_col, cellList);
        } else{
            extraCells = new Cell('>', start+size-1, row_col, extraCells);
            (*constraints)--;
        }
        r_occupied[row_col]++;
        c_occupied[start+size-1]++;
    }
    //Anything larger! Vertical Case!
    else{
        if (!state[start][row_col]){
            state[start][row_col] = cellList = new Cell('^', row_col, start, cellList);
        } else{
            extraCells = new Cell('<', row_col, start, extraCells);
            (*constraints)--;
        }
        r_occupied[start]++;
        c_occupied[row_col]++;

        for (int i = start+1; i < start+size-1; i++){
            if (!state[i][row_col]){
                state[i][row_col] = cellList = new Cell('X', row_col, i, cellList);
            } else{
                extraCells = new Cell('X', row_col, i, extraCells);
                (*constraints)--;
            }
            r_occupied[i]++;
            c_occupied[row_col]++;
        }

        if (!state[start+size-1][row_col]){
            state[start+size-1][row_col] = cellList = new Cell('v', row_col, start+size-1, cellList);
        } else{
            extraCells = new Cell('X', row_col, start+size-1, extraCells);
            (*constraints)--;
        }
        r_occupied[start+size-1]++;
        c_occupied[row_col]++;
    }

    childBoard = true;
    destroyed = false;
}

//Copy
Board::Board(const Board& other){
    rows = new int(*other.rows);
    cols = new int(*other.cols);
    r_sums = new int[*rows];
    c_sums = new int[*cols];
    for (int i = 0; i < *rows; i++) r_sums[i] = other.r_sums[i];
    for (int i = 0; i < *cols; i++) c_sums[i] = other.c_sums[i];
    constraints = new int(*other.constraints);

    cellList = NULL;
    extraCells = NULL;
    r_occupied = new int[*rows];
    c_occupied = new int[*cols];
    //Copy the occupied states over
    std::copy(other.r_occupied, other.r_occupied+(*rows), r_occupied);
    std::copy(other.c_occupied, other.c_occupied+(*cols), c_occupied);

    //Copy over the state and cellList
    state = new Cell**[*rows];
    for (int i = 0; i < *rows; i++){
        state[i] = new Cell*[*cols];
        for (int j = 0; j < *cols; j++){
            if (other.state[i][j] != NULL){
                state[i][j] = new Cell(*other.state[i][j]);
                state[i][j]->setLinkedCell(cellList);
                cellList = state[i][j];
            }
            else{
                state[i][j] = NULL;
            }
        }
    }

    childBoard = false;
    destroyed = false;
}

//Assignment
Board& Board::operator=(Board other){
    swap(*this, other);
    return *this;
}

//Swap
void swap(Battleship::Board& first, Battleship::Board& second){
    using std::swap;
    swap(first.state, second.state);
    swap(first.cellList, second.cellList);
    swap(first.rows, second.rows);
    swap(first.cols, second.cols);
    swap(first.r_occupied, second.r_occupied);
    swap(first.c_occupied, second.c_occupied);
    swap(first.r_sums, second.r_sums);
    swap(first.c_sums, second.c_sums);
    swap(first.childBoard, second.childBoard);
    swap(first.destroyed, second.destroyed);
}

Board::~Board(){
    if (!destroyed) destroyBoard();
}

void Board::destroyBoard(){
    for (Cell* nextCell; cellList != NULL;){
        nextCell = cellList->getLinkedCell();
        state[cellList->getY()][cellList->getX()] = NULL;
        r_occupied[cellList->getY()]--;
        c_occupied[cellList->getX()]--;
        delete cellList;
        cellList = nextCell;
    }

    for (Cell* nextCell; extraCells != NULL;){
        nextCell = extraCells->getLinkedCell();
        r_occupied[extraCells->getY()]--;
        c_occupied[extraCells->getX()]--;
        (*constraints)++;
        delete extraCells;
        extraCells = nextCell;
    }

    if (!childBoard){
        delete [] r_sums;
        delete [] c_sums;
        for (int i = 0; i < *rows; i++){
            delete [] state[i];
        }
        delete [] state;
        delete rows;
        delete cols;
        delete [] r_occupied;
        delete [] c_occupied;
        delete constraints;
    }
    destroyed = true;
}

bool Board::tryPlace(int x, int y, uint8_t ship, bool horizontal, Board*& newBoard){
    //quick check for validity and ship size
    if (x < 0 || y < 0 || ship > 7 || ship < 1
        || (horizontal && (y >= *rows || x + ship > *cols ))
        || (!horizontal && (x >= *cols || y + ship > *rows))) return false;

    //Check the legality of a move (Two ternaries determine stuff
    if(!checkLegality((horizontal) ? x : y, ship,
                      (horizontal) ? y : x, horizontal)) return false;

    newBoard = new Board(*this, (horizontal) ? x : y, ship,
                                (horizontal) ? y : x, horizontal);

    return true;
}

inline bool Board::checkLegality(int start, uint8_t size, int row_col, bool horizontal) const{
    //Horizontal case
    char type;
    uint8_t repeatCheck = 0;
    if (horizontal){
        if(size > getRowSpaceLeft(row_col)) return false;
        if (checkCell(start-1, row_col-1) > ' '
            || checkCell(start-1, row_col) > ' '
            || checkCell(start-1, row_col+1) > ' ') return false;
        for (int i = start; i < start+size; i++){
            if (checkCell(i, row_col-1) > ' '
                || checkCell(i, row_col+1) > ' ') return false;
            if ((type = checkCell(i, row_col)) != '\0'){
                if (size == 1) return false;
                if (i == start && type == '<'){
                    repeatCheck++;
                    continue;
                }
                if (i == start+size-1 && type == '>'){
                    repeatCheck++;
                    continue;
                }
                if (type == 'X' && (start < i && i < start+size-1)){
                    repeatCheck++;
                    continue;
                }
                return false;
            }
            if (!getColSpaceLeft(i)) return false;
        }
        if (repeatCheck == size) return false;
        if (checkCell(start+size, row_col-1) > ' '
            || checkCell(start+size, row_col) > ' '
            || checkCell(start+size, row_col+1) > ' ') return false;
        return true;
    }
    //vertical case
    if(size > getColSpaceLeft(row_col)) return false;
    if (checkCell(row_col-1, start-1) > ' '
        || checkCell(row_col, start-1) > ' '
        || checkCell(row_col+1, start-1) > ' ') return false;
    for (int i = start; i < start+size; i++){
        if (checkCell(row_col-1, i) > ' '
            || checkCell(row_col+1, i) > ' ') return false;
        if ((type = checkCell(row_col, i)) != '\0'){
            if (size == 1) return false;
            if (i == start && type == '^'){
                repeatCheck++;
                continue;
            }
            if (i == start+size-1 && type == 'v'){
                repeatCheck++;
                continue;
            }
            if (type == 'X' && (start < i && i < start+size-1)){
                repeatCheck++;
                continue;
            }
            return false;
        }
        if (!getRowSpaceLeft(i)) return false;
    }
    if (repeatCheck == size) return false;
    if (checkCell(row_col-1, start+size) > ' '
        || checkCell(row_col, start+size) > ' '
        || checkCell(row_col+1, start+size) > ' ') return false;
    return true;
}

inline char Board::checkCell(int x, int y) const{
    //One massive ternary statement
    return (x < 0 || x >= *cols || y < 0 || y >= *rows
            || state[y][x] == NULL) ? '\0' : state[y][x]->getType();
}

void Board::printBoard(std::ostream& out) const{
    out << "+" << std::string(*cols, '-') << "+" << std::endl;
    for (int i = 0; i < *rows; i++){
        out << "|";
        for (int j = 0; j < *cols; j++){
            char c = checkCell(j, i);
            out << ((c == '\0') ? ' ' : c);
        }
        out << "|";
        if (r_sums[i] > *cols) out << '?';
        else out << r_sums[i];
        out << std::endl;
    }
    out << "+" << std::string(*cols, '-') << "+" << std::endl << " ";
    for (int i = 0; i < *cols; i++){
        if (c_sums[i] > *rows) out << '?';
        else out << c_sums[i];
    }
    out << std::endl;
}

int Board::getRowSpaceLeft(int row) const{
    return r_sums[row] - r_occupied[row];
}

int Board::getColSpaceLeft(int col) const{
    return c_sums[col] - c_occupied[col];
}

int Board::getLargestLeft() const{
    int largest = 0;
    for (int i = 0; i < *rows; i++)
        if (getRowSpaceLeft(i) > largest) largest = getRowSpaceLeft(i);
    for (int i = 0; i < *cols; i++)
        if (getColSpaceLeft(i) > largest) largest = getColSpaceLeft(i);
    return largest;
}

bool Board::isFilled() const{
    if (*constraints > 0) return false;
    for (int i = 0; i < *rows; i++)
        if (r_sums[i] <= *cols && getRowSpaceLeft(i)) return false;
    for (int i = 0; i < *cols; i++)
        if (c_sums[i] <= *rows && getColSpaceLeft(i)) return false;
    return true;
}

Ship* Board::computeCompletedShips(){
    if (childBoard) return NULL;
    Ship* existingShips = NULL;
    for (int i = 0; i < *rows; i++) r_occupied[i] = (r_sums[i] > *cols);
    for (int i = 0; i < *cols; i++) c_occupied[i] = (c_sums[i] > *rows);
    for (Cell* cell = cellList; cell != NULL; cell = cell->getLinkedCell()){
        char type = cell->getType();
        if (type == 'o'){
            existingShips = new Ship(1, cell->getX(), cell->getY(), true, existingShips);
            c_occupied[cell->getX()]++;
            r_occupied[cell->getY()]++;
        }
        else if (type == '^'){
            for (short i = 1; i < 8; i++){
                if (checkCell(cell->getX(),cell->getY()+i) == 'X'){
                    continue;
                }
                if (checkCell(cell->getX(),cell->getY()+i) == 'v'){
                    existingShips = new Ship(i+1, cell->getX(),cell->getY(),
                                             false, existingShips);
                    c_occupied[cell->getX()] += i+1;
                    for (short j = 0; j <= i; j++){
                        r_occupied[cell->getY()+j]++;
                    }
                }
                break;
            }
        }
        else if (type == '<'){
            for (short i = 1; i < 8; i++){
                if (checkCell(cell->getX()+i,cell->getY()) == 'X'){
                    continue;
                }
                if (checkCell(cell->getX()+i,cell->getY()) == '>'){
                    existingShips = new Ship(i+1, cell->getX(),cell->getY(),
                                             true, existingShips);
                    r_occupied[cell->getY()] += i+1;
                    for (short j = 0; j <= i; j++){
                        c_occupied[cell->getX()+j]++;
                    }
                }
                break;
            }
        }
    }
    return existingShips;
}

}
