package model;

import controller.Simulation;
import matrix.Coordinate;
import matrix.ListMatrix;
import javafx.scene.paint.Color;

import java.util.Iterator;
import java.util.Random;



/**
 * {@link CellularAutomatonSimulation} instances run <i>The Game of Life</i>.
 *
 *  @param <S> The type of state used in the simulation.
 */
public class CellularAutomatonSimulation<S extends State<S>>
        implements Simulation {

    private final ListMatrix<Cell<S>> grid;
    private final Cell<Integer> generationNumber = new Cell<>(0);
    private final CellularAutomaton<S> automaton;
    private final Random generator;

    /**
     * Creates a new {@link CellularAutomatonSimulation} instance for a given automaton.
     *
     * @param automaton  A description of the {@link CellularAutomaton}.
     * @param generator  The {@link Random} instance used for random state generation.
     */
    public CellularAutomatonSimulation(CellularAutomaton<S> automaton, Random generator) {
        this.automaton = automaton;
        this.grid = new ListMatrix<>(
                automaton.numberOfColumns(),
                automaton.numberOfRows(),
                new ConstantCellInitializer<>(automaton.defaultState())
        );
        this.generator = generator;
    }


    @Override
    public int numberOfColumns() {
        return this.automaton.numberOfColumns();
    }

    @Override
    public int numberOfRows() {
        return this.automaton.numberOfRows();
    }

    /**
     * Returns the {@link Cell} at the specified coordinate.
     *
     * @param coordinate The coordinate of the cell to retrieve.
     * @return The cell at the specified coordinate.
     */
    public Cell<S> at(Coordinate coordinate) {
        return this.grid.get(coordinate);
    }

    @Override
    public void updateToNextGeneration() {
        ListMatrix<S> nextGeneration= nextGenerationMatrix();
        for(Coordinate coordinate: this.grid.coordinates()){
            S next= nextGeneration.get(coordinate);
            Cell<S> cell = this.grid.get(coordinate);
            cell.set(next);
        }
        this.generationNumber.set(this.generationNumber.get()+1);
        }

    /** Computes the {@link ListMatrix} of states obtained after a single step of updates
     * of the simulation.
     *
     * @return the states of each cell after one generation
     */
    private ListMatrix<S> nextGenerationMatrix() {
        return new ListMatrix<>(
            this.numberOfColumns(),this.numberOfRows(),new NextGenerationInitializer<>(this));
    }
    @Override
    public void next(Coordinate coordinate) {
        Cell <S> cell =this.at(coordinate);
        S next = cell.get().next();
        cell.set(next);
    }

    @Override
    public void copy(Coordinate source, Coordinate destination) {
        S value =this.at(source).get();
        this.at(destination).set(value);
    }

    @Override
    public Color getColor(Coordinate coordinate) {
        return this.at(coordinate).get().getColor();
    }

    @Override
    public void setChangeListener(Coordinate coordinate, Runnable listener) {
        this.at(coordinate).addOnChangeListener(
                (oldValue, newValue) -> listener.run()
        );
    }

    @Override
    public void setGenerationNumberChangeListener(OnChangeListener<Integer> listener){
        this.generationNumber.addOnChangeListener(listener);
    }


    @Override
    public void clear() {
        for(Coordinate coordinate : this.grid.coordinates()){
            this.at(coordinate).set(this.automaton.defaultState());
        }
        this.generationNumber.set(0);
        }


    @Override
    public void reset() {
        for(Coordinate coordinate:this.grid.coordinates()){
            this.at(coordinate).set(this.automaton.randomState(this.generator));
        }
        this.generationNumber.set(0);
            }

    @Override
    public Iterator<Coordinate> iterator() {
        return this.grid.coordinates().iterator();
    }


   




  
}