#include <string>

struct BoundaryCondition
{
    virtual bool boundary(Board& board, int i, int j) = 0;
};

class Board {
    public :
        // Konstruktor, erzeuge bool* _fields
        Board(int width, int height, BoundaryCondition cond);

        // Desktruktor, raeume bool* _fields auf
        ~Board();
        
        // copy constructor, intializes by copying from 'other'
        Board(Board& other);

        // assignment operator
        Board& operator=(Board& other);

        // gibt die Breite des Bretts zurueck
        double width();

        // gibt die Hoehe des Bretts zurueck
        double height();

        // schreibe value an den Pixel (i,j)
        // Ueberlegen Sie wie aus (i,j) den flachen Index bei row-major bekommen
        void updateField(int i, int j, bool value);

        // Zugang zum Pixel (i,j) im 1D Array
        // Ueberlegen Sie wie aus (i,j) den flachen Index bei row-major bekommen
        int operator()(int i, int j);

        // Gibt zurueck ob Feld am Rand liegt
        bool touchesBoundary(int i, int j);

        // Gibt Zahl der lebenden Zellen zurueck, die das Feld umgeben
        int livingCells(int i, int j);

        // Prueft ob Zelle lebt oder nicht
        bool isLiving(int i, int j);

        // Gibt die BoundaryCondition zurueck
        BoundaryCondition boundaryCondition();

    private :
        int _width;
        int _height;
        bool* _fields;
        void copy(Board& other);
        BoundaryCondition _boundCond;
};

Board::Board(int width, int height, BoundaryCondition cond) {
    // initialize all values
    _width = width;
    _height = height;
    _boundCond = cond;
    _fields = new bool[width * height];
}

Board::~Board() {
    _fields = 0;
}

void Board::copy(Board& other) {
    _width = other.width();
    _height = other.height();
    _boundCond = other.boundaryCondition();
    _fields = new bool[_width * _height];
    for (int i = 0; i < _height; i++) {
        for (int j = 0; j < _width; j++) {
            updateField(i, j, other.isLiving(i, j));
        }
    }
}

Board::Board(Board& other) {
    copy(other);
}

Board& Board::operator=(Board& other) {
    if (this == &other) {
        return *this;
    }
    copy(other);
    return *this;
}

double Board::width() {
    return _width;
}

double Board::height() {
    return _height;
}

BoundaryCondition Board::BoundaryCondition() {
    return _boundCond;
}

void Board::updateField(int i, int j, bool value) {
    // i = Zeile und wegen row-major Konvention muss i mit der Breite
    // multipliziert werden
    _fields[i * _width + j] = value;
}

bool Board::touchesBoundary(int i, int j) {
    return i == _height - 1 || i == -1 || j == _width - 1 || j == -1;
}

int Board::livingCells(int i, int j) {
    bool surround[8] = { isLiving(i+1, j+1), isLiving(i, j+1), isLiving(i-1, j+1)
                     , isLiving(i+1, j), isLiving(i-1, j)
                     , isLiving(i+1, j-1), isLiving(i, j-1), isLiving(i-1, j-1)};
    int numLiving = 0;
    for (int i = 0; i < 8; i++) {
        if (surround[i] == true) {
            numLiving += 1;
        }
    }
}

bool Board::isLiving(int i, int j) {
    if (touchesBoundary(i, j)) {
        return _boundCond.boundary(i, j);
    } else {
        return _fields[i * _width + j];
    }
}

int Board::operator()(int i, int j) {
    // i = Zeile und wegen row-major Konvention muss i mit der Breite
    // Pixel multipliziert werden
    return _fields[i * _width+ j];
}
