public abstract class CellularAutomaton<R extends Rule, S extends Space, T extends Time, O extends Combination,
        N extends Transition> {

    private R rule;

    protected CellularAutomaton(R rule) throws InvalidRuleException {
        if (rule == null) {
            throw new InvalidRuleException();
        }
        this.rule = rule;
    }

    public void executeRule(S space, T time) throws InvalidStateException, InvalidCombinationException,
            InvalidTransitionException, TimeLimitReachedException {
        O combination = (O) space.getCombination(time);
        N transition = (N) this.rule.getTransition(combination);
        space.setState(time, transition);
        time.increase();
    }

    public R getRule() {
        return this.rule;
    }

} public abstract class Combination<A extends State> {

    private A reference;
    private List<A> neighborhood;

    protected Combination(A reference, A... neighbors) throws InvalidStateException {
        if (reference == null || neighbors == null) {
            throw new InvalidStateException();
        }
        this.reference = reference;
        this.neighborhood = new ArrayList<A>();
        for (int i = 0; i < neighbors.length; i++) {
            this.neighborhood.add(neighbors[i]);
        }
    }

    public A getReferenceState() {
        return this.reference;
    }

    public List<A> getNeighborhood() {
        return this.neighborhood;
    }
} public class DimensionalCellularAutomaton extends CellularAutomaton<DimensionalRule, DimensionalSpace, DimensionalTime,
		DimensionalCombination,DimensionalTransition> {

	public DimensionalCellularAutomaton(DimensionalRule rule) throws InvalidRuleException {
		super(rule);
	}

} public final class DimensionalCombination extends Combination<DimensionalState> {

	public DimensionalCombination(DimensionalState reference, DimensionalState... neighbors) throws InvalidStateException {
		super(reference, neighbors);
	}

} public class DimensionalRule extends Rule<DimensionalState,DimensionalTransition,DimensionalCombination> {

	public DimensionalRule(DimensionalTransition ... transitions) throws InvalidTransitionException {
		super(transitions);
	}

} public final class DimensionalState extends State {

	public DimensionalState(String name, int value) {
		super(name, value);
	}

} public final class DimensionalTransition extends Transition<DimensionalCombination,DimensionalState> {

	public DimensionalTransition(DimensionalCombination combination, DimensionalState state) throws InvalidCombinationException, InvalidStateException {
		super(combination, state);
	}

} public abstract class Rule<A extends State, N extends Transition, O extends Combination> {

    private Map<A, List<N>> transitions;

    protected Rule(N... transitions) throws InvalidTransitionException {
        if (transitions == null) {
            throw new InvalidTransitionException();
        }
        initialize(transitions);
    }

    private void initialize(N... transitions) {
        this.transitions = new HashMap<A, List<N>>();
        for (N t : transitions) {
            A s = (A) t.getCombination().getReferenceState();
            if (this.transitions.containsKey(s)) {
                this.transitions.get(s).add(t);
            } else {
                ArrayList<N> l = new ArrayList<N>();
                l.add(t);
                this.transitions.put(s, l);
            }
        }
    }

    public N getTransition(O combination) throws InvalidStateException, InvalidCombinationException {
        A s = (A) combination.getReferenceState();
        List<N> l = this.transitions.get(s);
        if (l == null) {
            throw new InvalidStateException();
        }
        for (N t : l) {
            boolean found = true;
            for (int i = 0; i < combination.getNeighborhood().size(); i++) {
                A neighborCombination = (A) combination.getNeighborhood().get(i);
                A neighborTransition = (A) t.getCombination().getNeighborhood().get(i);
                if (!neighborCombination.equals(neighborTransition)) {
                    found = false;
                    break;
                }
            }
            if (found) {
                return t;
            }
        }
        throw new InvalidCombinationException();
    }

    public Map<A, List<N>> getTransitions() {
        return this.transitions;
    }

} public abstract class State {

    private String name;
    private int value;

    protected State(String name, int value) {
        if (name == null) {
            throw new IllegalArgumentException();
        }
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return this.name;
    }

    public int getValue() {
        return this.value;
    }

} public abstract class Transition<O extends Combination, A extends State> {

    private O combination;
    private A state;

    protected Transition(O combination, A state) throws InvalidCombinationException, InvalidStateException {
        if (combination == null) {
            throw new InvalidCombinationException();
        }
        if (state == null) {
            throw new InvalidStateException();
        }
        this.combination = combination;
        this.state = state;
    }

    public O getCombination() {
        return this.combination;
    }

    public A getState() {
        return this.state;
    }

} public class InvalidAbsoluteTimeLimitException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 8503779819489685979L;

} public class InvalidCellularAutomataException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = -4104738409967924193L;

} public class InvalidCombinationException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 884762124844706448L;

} public class InvalidDimensionalAmountException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 1514291655215196494L;

} public class InvalidDimensionalSpaceException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = -859556206406629948L;

} public class InvalidInitialConditionException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 3070196019858178183L;

} public class InvalidRelativeTimeLimitException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = -8421027467680752602L;

} public class InvalidRuleException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 5840928619484335072L;

} public class InvalidSpaceException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 9045617613507195324L;

} public class InvalidStateException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = -7435119307025647318L;

} public class InvalidTimeException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 4810377526354040415L;

} public class InvalidTransitionException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = 1848434169756515614L;

} public class InvalidUniverseException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = -5140302667570723804L;

} public class TimeLimitReachedException extends Exception {

    /**
     * Serialization management
     */
    private static final long serialVersionUID = -6359890788325087840L;

} public abstract class Cell<A extends State, N extends Transition> {

    protected A state;
    protected N transition;

    protected Cell(N transition) throws InvalidTransitionException {
        if (transition == null) {
            throw new InvalidTransitionException();
        }
        this.transition = transition;
        this.state = (A) transition.getState();
    }

    protected Cell(A state) throws InvalidStateException {
        if (state == null) {
            throw new InvalidStateException();
        }
        this.state = state;
    }

    public A getState() {
        return state;
    }

    public N getTransition() {
        return this.transition;
    }

} public final class DimensionalCell extends Cell<DimensionalState, DimensionalTransition> {

	public DimensionalCell(DimensionalTransition transition) throws InvalidTransitionException {
		super(transition);
	}

	public DimensionalCell(DimensionalState state) throws InvalidStateException {
		super(state);
	}
} public class DimensionalSpace extends Space<Cell,DimensionalTime,DimensionalTransition,DimensionalCombination> {

	public DimensionalSpace(DimensionalTime time, List<Cell> initialCondition, boolean keepHistory)
			throws InvalidDimensionalAmountException, InvalidInitialConditionException, InvalidDimensionalSpaceException {
		super(time, initialCondition, keepHistory);
	}

	@Override
	protected DimensionalCombination getCombination(DimensionalTime time, List<Cell> space) {
		// Since this method is declared as abstract in core module, its
		// implementation and tests should be created inside each dimensional
		// module. The implementation here (if any) is used since required to
		// test other structures.
		return null;
	}

	@Override
	protected void initialize() {
		// Since this method is declared as abstract in core module, its
		// implementation and tests should be created inside each dimensional
		// module. The implementation here (if any) is used since required to
		// test other structures.
		if (super.keepHistory) {
			super.history = new ArrayList<List<Cell>>();
		}
		super.current = new ArrayList<Cell>();
	}

	@Override
	protected void createNewIteration(DimensionalTime time) {
		// Since this method is declared as abstract in core module, its
		// implementation and tests should be created inside each dimensional
		// module. The implementation here (if any) is used since required to
		// test other structures.
	}

	@Override
	protected void createNewCell(DimensionalTime time, DimensionalTransition transition) {
		// Since this method is declared as abstract in core module, its
		// implementation and tests should be created inside each dimensional
		// module. The implementation here (if any) is used since required to
		// test other structures.
	}

} public class DimensionalTime extends Time {

	public DimensionalTime(final int limit, int... limits)
			throws InvalidAbsoluteTimeLimitException, InvalidRelativeTimeLimitException, CloneNotSupportedException {
		super(limit, limits);
	}

} public final class DimensionalUniverse extends Universe<DimensionalSpace,DimensionalTime> {

	public DimensionalUniverse(DimensionalSpace space, DimensionalTime time) throws InvalidSpaceException, InvalidTimeException {
		super(space, time);
	}

} public abstract class Space<X, T extends Time, N extends Transition, O extends Combination> {

    protected List<X> initial;
    protected List<List<X>> history;
    protected List<X> last;
    protected List<X> current;
    protected int dimensionalAmount;
    protected boolean keepHistory;
    protected boolean rotating;

    protected Space(T time, List<X> initialCondition, boolean keepHistory)
            throws InvalidDimensionalAmountException, InvalidInitialConditionException, InvalidDimensionalSpaceException {
        if (time.getRelative() != null && !time.getRelative().isEmpty()) {
            this.dimensionalAmount = time.getRelative().size();
        } else {
            throw new InvalidDimensionalAmountException();
        }
        if (initialCondition != null && !initialCondition.isEmpty()) {
            validateDimensions(initialCondition, this.dimensionalAmount);
            this.initial = initialCondition;
        } else {
            throw new InvalidInitialConditionException();
        }
        this.keepHistory = keepHistory;
        this.rotating = false;
        initialize();
    }

    protected void validateDimensions(List<X> space, int dimensionalAmount) throws InvalidDimensionalSpaceException {
        if (dimensionalAmount == 1) {
            if (!space.isEmpty() && !(space.get(0) instanceof Cell)) {
                throw new InvalidDimensionalSpaceException();
            }
        } else if (!space.isEmpty() && space.get(0) instanceof List) {
            validateDimensions((List) space.get(0), dimensionalAmount--);
        } else {
            throw new InvalidDimensionalSpaceException();
        }
    }

    public O getCombination(T time) throws InvalidStateException {
        if (time.getAbsolute() == 0) {
            return getCombination(time, this.initial);
        } else {
            return getCombination(time, this.last);
        }
    }

    public void setState(T time, N transition) throws InvalidTransitionException {
        if (this.rotating) {
            this.rotating = false;
            createNewIteration(time);
        }
        createNewCell(time, transition);
        if (needsNewIteration(time)) {
            rotate();
        }

    }

    private boolean needsNewIteration(T time) {
        for (Time r : time.getRelative()) {
            if (time.getAbsolute() == time.getLimit() - 1 || r.getAbsolute() != r.getLimit() - 1) {
                return false;
            }
        }
        return true;
    }

    private void rotate() {
        this.rotating = true;
        ArrayList<X> currentClone = (ArrayList) ((ArrayList) this.current).clone();
        if (this.keepHistory) {
            this.history.add(currentClone);
        }
        this.last = currentClone;
    }

    public List<X> getInitial() {
        return this.initial;
    }

    public List<List<X>> getHistory() {
        return this.history;
    }

    public List<X> getLast() {
        return this.last;
    }

    public List<X> getCurrent() {
        return this.current;
    }

    public int getDimensionalAmount() {
        return this.dimensionalAmount;
    }

    public boolean isKeepHistory() {
        return this.keepHistory;
    }

    protected abstract void initialize();

    protected abstract O getCombination(T time, List<X> space) throws InvalidStateException;

    protected abstract void createNewIteration(T time);

    protected abstract void createNewCell(T time, N transition) throws InvalidTransitionException;

} public abstract class Time implements Cloneable {

    private AtomicInteger absoluteTime;
    private List<Time> relativeTime;
    private AtomicInteger limit;

    protected Time() {
        this.absoluteTime = new AtomicInteger();
    }

    protected Time(final int limit) throws InvalidAbsoluteTimeLimitException {
        initializeLimit(limit);
        this.absoluteTime = new AtomicInteger();
    }

    protected Time(final int limit, int... limits)
            throws InvalidAbsoluteTimeLimitException, InvalidRelativeTimeLimitException, CloneNotSupportedException {
        initializeLimit(limit);
        initializeRelativeTime(limits);
        this.absoluteTime = new AtomicInteger();
    }

    private void initializeRelativeTime(int... limits) throws InvalidRelativeTimeLimitException, CloneNotSupportedException {
        if (limits != null && limits.length > 0) {
            ArrayList<Time> relTimTmp = new ArrayList<Time>();
            for (int i = 0; i < limits.length; i++) {
                try {
                    Time t = (Time) this.clone();
                    t.initializeLimit(limits[i]);
                    t.absoluteTime = new AtomicInteger();
                    relTimTmp.add(t);
                } catch (InvalidAbsoluteTimeLimitException e) {
                    throw new InvalidRelativeTimeLimitException();
                }
            }
            this.relativeTime = (List<Time>) relTimTmp.clone();
        } else {
            throw new InvalidRelativeTimeLimitException();
        }
    }

    private void initializeLimit(int limit) throws InvalidAbsoluteTimeLimitException {
        if (limit > 0) {
            this.limit = new AtomicInteger(limit);
        } else {
            throw new InvalidAbsoluteTimeLimitException();
        }
    }

    /**
     * The complete evolution of time is the multiplication of the limit of
     * absolute time by the product of all limits of relative times, minus one.
     * Removing one is necessary since the initial time (0) is used. Without
     * removing one, we have the formula that calculates the amount of times
     * the rule will be executed to reach complete evolution.
     * - lim(a) = limit of absolute time
     * - d = amount of dimensions (or relative times)
     * - lim(ri) = limit of a relative time i
     * LaTeX formula: T = ${lim(a)\displaystyle \left(\prod_{i=1}^{d} lim(r_{i})\right)-1}$
     *
     * @throws TimeLimitReachedException
     */
    public void increase() throws TimeLimitReachedException {
        ArrayList<Time> resetPool = new ArrayList<Time>();
        if (this.relativeTime != null) {
            for (int i = this.relativeTime.size(); i > 0; i--) {
                Time r = this.relativeTime.get(i - 1);
                try {
                    r.increase();
                    for (Time t : resetPool) {
                        t.resetAbsoluteTime();
                    }
                    return;
                } catch (TimeLimitReachedException e) {
                    resetPool.add(r);
                }
            }
        }
        if (this.limit == null || this.absoluteTime.get() < this.limit.get() - 1) {
            this.absoluteTime.incrementAndGet();
            for (Time t : resetPool) {
                t.resetAbsoluteTime();
            }
        } else {
            throw new TimeLimitReachedException();
        }
    }

    protected void resetAbsoluteTime() {
        this.absoluteTime.set(0);
    }

    public int getAbsolute() {
        return this.absoluteTime.get();
    }

    public List<Time> getRelative() {
        return this.relativeTime;
    }

    public int getLimit() {
        return this.limit.get();
    }

} public abstract class Universe<S extends Space, T extends Time> {

    private S space;
    private T time;

    protected Universe(S space, T time) throws InvalidSpaceException, InvalidTimeException {
        if (space == null) {
            throw new InvalidSpaceException();
        }
        if (time == null) {
            throw new InvalidTimeException();
        }
        this.space = space;
        this.time = time;
    }

    public S getSpace() {
        return space;
    }

    public T getTime() {
        return time;
    }

}
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import com.ghjansen.cas.core.ca.CellularAutomaton;
import com.ghjansen.cas.core.ca.Combination;
import com.ghjansen.cas.core.ca.DimensionalCellularAutomaton;
import com.ghjansen.cas.core.ca.DimensionalCombination;
import com.ghjansen.cas.core.ca.DimensionalRule;
import com.ghjansen.cas.core.ca.DimensionalState;
import com.ghjansen.cas.core.ca.DimensionalTransition;
import com.ghjansen.cas.core.ca.Rule;
import com.ghjansen.cas.core.ca.State;
import com.ghjansen.cas.core.ca.Transition;
import com.ghjansen.cas.core.exception.InvalidAbsoluteTimeLimitException;
import com.ghjansen.cas.core.exception.InvalidCellularAutomataException;
import com.ghjansen.cas.core.exception.InvalidCombinationException;
import com.ghjansen.cas.core.exception.InvalidDimensionalAmountException;
import com.ghjansen.cas.core.exception.InvalidDimensionalSpaceException;
import com.ghjansen.cas.core.exception.InvalidInitialConditionException;
import com.ghjansen.cas.core.exception.InvalidRelativeTimeLimitException;
import com.ghjansen.cas.core.exception.InvalidRuleException;
import com.ghjansen.cas.core.exception.InvalidSpaceException;
import com.ghjansen.cas.core.exception.InvalidStateException;
import com.ghjansen.cas.core.exception.InvalidTimeException;
import com.ghjansen.cas.core.exception.InvalidTransitionException;
import com.ghjansen.cas.core.exception.InvalidUniverseException;
import com.ghjansen.cas.core.exception.TimeLimitReachedException;
import com.ghjansen.cas.core.physics.Cell;
import com.ghjansen.cas.core.physics.DimensionalCell;
import com.ghjansen.cas.core.physics.DimensionalSpace;
import com.ghjansen.cas.core.physics.DimensionalTime;
import com.ghjansen.cas.core.physics.DimensionalUniverse;
import com.ghjansen.cas.core.physics.Space;
import com.ghjansen.cas.core.physics.Time;
import com.ghjansen.cas.core.physics.Universe;
/*
 * CAS - Cellular Automata Simulator
 * Copyright (C) 2016  Guilherme Humberto Jansen
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.ghjansen.cas.core.simulation;





/**
 * @author Guilherme Humberto Jansen (contact.ghjansen@gmail.com)
 */
public class SimulationTest {

	@Test
	public void dimensionalSimulationConstructor() throws InvalidStateException, InvalidCombinationException,
			InvalidTransitionException, InvalidRuleException, CloneNotSupportedException,
			InvalidAbsoluteTimeLimitException, InvalidRelativeTimeLimitException, InvalidDimensionalAmountException,
			InvalidInitialConditionException, InvalidDimensionalSpaceException, TimeLimitReachedException,
			InvalidSpaceException, InvalidTimeException, InvalidUniverseException, InvalidCellularAutomataException {
		final DimensionalState dimensionalBlackState = new DimensionalState("black", 0);
		final DimensionalState dimensionalWhiteState = new DimensionalState("white", 1);
		final DimensionalCombination dimensionalCombination0 = new DimensionalCombination(dimensionalBlackState,
				dimensionalBlackState, dimensionalBlackState);
		final DimensionalTransition dimensionalTransition0 = new DimensionalTransition(dimensionalCombination0,
				dimensionalWhiteState);
		final DimensionalRule dimensionalRule = new DimensionalRule(dimensionalTransition0);
		final DimensionalCellularAutomaton dimensionalCellularAutomaton = new DimensionalCellularAutomaton(dimensionalRule);
		final DimensionalTime dimensionalTime = new DimensionalTime(1, 3);
		final Cell dimensionalCell = getNewBlackDimensionalCell(dimensionalBlackState);
		final List<Cell> initialCondition = new ArrayList<Cell>();
		initialCondition.add(dimensionalCell);
		initialCondition.add(dimensionalCell);
		initialCondition.add(dimensionalCell);
		final DimensionalSpace dimensionalSpace = new DimensionalSpace(dimensionalTime, initialCondition, true);
		final Universe dimensionalUniverse = new DimensionalUniverse(dimensionalSpace, dimensionalTime);