package org.rubyspa.droidgol;

import android.util.Pair;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class Game {

    private final Pair<Integer, Integer> dimensions;
    private final Random random = new Random();
    private Map<Pair<Integer, Integer>, Boolean> state;

    public Game(final Integer width, final Integer height) {
        this.dimensions = Pair.create(width, height);
        this.state = randomState();
    }

    public Game(final Integer width, final Integer height, final Map state) {
        this.dimensions = Pair.create(width, height);
        this.state = initializeState(state);
    }

    private Map<Pair<Integer, Integer>, Boolean> randomState() {
        Map<Pair<Integer, Integer>, Boolean> randomState = new HashMap<Pair<Integer, Integer>, Boolean>();
        final Integer width = dimensions.first;
        final Integer height = dimensions.second;
        for (int row = 1; row <= height; row++) {
            for (int column = 1; column <= width; column++) {
                randomState.put(Pair.create(column, row), random.nextBoolean());
            }
        }
        return Collections.unmodifiableMap(randomState);
    }

    private Map<Pair<Integer, Integer>, Boolean> initializeState(final Map<Pair<Integer, Integer>, Boolean> state) {
        Map<Pair<Integer, Integer>, Boolean> newState = new HashMap<Pair<Integer, Integer>, Boolean>();
        final Integer width = dimensions.first;
        final Integer height = dimensions.second;
        for (int row = 1; row <= height; row++) {
            for (int column = 1; column <= width; column++) {
                Pair<Integer, Integer> coordinates = Pair.create(column, row);
                if (state.get(coordinates) == Boolean.TRUE) {
                    newState.put(coordinates, Boolean.TRUE);
                } else {
                    newState.put(coordinates, Boolean.FALSE);
                }
            }
        }
        return Collections.unmodifiableMap(newState);
    }

    public Pair<Integer, Integer> dimensions() {
        return dimensions;
    }

    public boolean stateAt(Pair<Integer, Integer> coordinates) {
        return state.get(wrapAroundBorders(coordinates));
    }

    private Pair<Integer, Integer> wrapAroundBorders(Pair<Integer, Integer> unwrapped) {
        int x_wrapped = unwrapped.first < 1 ? dimensions.first : unwrapped.first;
        x_wrapped = x_wrapped > dimensions.first ? 1 : x_wrapped;
        int y_wrapped = unwrapped.second < 1 ? dimensions.second : unwrapped.second;
        y_wrapped = y_wrapped > dimensions.second ? 1 : y_wrapped;
        return Pair.create(x_wrapped, y_wrapped);
    }

    public Integer neighbors(Pair<Integer, Integer> coordinates) {
        int neighbors = 0;
        for (int x = coordinates.first - 1; x <= coordinates.first + 1; x++) {
            for (int y = coordinates.second - 1; y <= coordinates.second + 1; y++) {
                if (stateAt(Pair.create(x, y)) == Boolean.TRUE) {
                    neighbors++;
                }
            }
        }
        if (stateAt(coordinates) == Boolean.TRUE) neighbors--;
        return neighbors;
    }

    public Game evolve() {
        Map<Pair<Integer, Integer>, Boolean> nextState = new HashMap<Pair<Integer, Integer>, Boolean>();
        for (Pair<Integer, Integer> coordinates : state.keySet()) {
            nextState.put(coordinates, Rules.apply(neighbors(coordinates), stateAt(coordinates)));
        }
        this.state = Collections.unmodifiableMap(nextState);
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Game game = (Game) o;

        if (!state.equals(game.state)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return state.hashCode();
    }
}

@@ -2065,222 +2065,142 @@
 lic 
-Pair%3CInteger, Integer%3E dimensions() %7B%0A        return dimensions;%0A    %7D%0A%0A    public boolean stateAt(Pair%3CInteger, Integer%3E coordinates) %7B%0A        return sta
+Game evolve() %7B%0A        Map%3CPair%3CInteger, Integer%3E, Boolean%3E nextState = new HashMap%3CPair%3CIn
 te
-.
 ge
-t(wrapAroundBorders(coordinates)
+r, Integer%3E, Boolean%3E(
 );%0A    
-%7D%0A%0A
     
-private 
+for (
 Pair
@@ -2222,410 +2222,229 @@
 er%3E 
-wrapAroundBorders(Pair%3CInteger, Integer%3E unwrapped) %7B%0A        int x_wrapped = unwrapped.first %3C 1 ? dimensions.first : unwrapped.first;%0A        x_wrapped = x_wrapped %3E dimensions.first ? 1 : x_wrapped;%0A        int y_wrapped = unwrapped.second %3C 1 ? dimensions.second : unwrapped.second;%0A        y_wrapped = y_wrapped %3E dimensions.second ? 1 : y_wrapped;%0A        return Pair.create(x_wrapped, y_wrapped)
+coordinates : state.keySet()) %7B%0A            nextState.put(coordinates, Rules.apply(neighbors(coordinates), stateAt(coordinates)));%0A        %7D%0A        this.state = Collections.unmodifiableMap(nextState);%0A        return this
 ;%0A  
@@ -2958,366 +2958,621 @@
 lic 
-Game evolve() %7B%0A        Map%3CPair%3CInteger, Integer%3E, Boolean%3E nextState = new HashMap%3CPair%3CInteger, Integer%3E, Boolean%3E();%0A        for (Pair%3CInteger, Integer%3E coordinates : state.keySet()) %7B%0A            nextState.put(coordinates, Rules.apply(neighbors(coordinates), stateAt(coordinates)));%0A        %7D%0A        this.state = Collections.unmodifiableMap(nextState);
+boolean stateAt(Pair%3CInteger, Integer%3E coordinates) %7B%0A        return state.get(wrapAroundBorders(coordinates));%0A    %7D%0A%0A    private Pair%3CInteger, Integer%3E wrapAroundBorders(Pair%3CInteger, Integer%3E unwrapped) %7B%0A        int x_wrapped = unwrapped.first %3C 1 ? dimensions.first : unwrapped.first;%0A        x_wrapped = x_wrapped %3E dimensions.first ? 1 : x_wrapped;%0A        int y_wrapped = unwrapped.second %3C 1 ? dimensions.second : unwrapped.second;%0A        y_wrapped = y_wrapped %3E dimensions.second ? 1 : y_wrapped;%0A        return Pair.create(x_wrapped, y_wrapped);%0A    %7D%0A%0A    public Pair%3CInteger, Integer%3E dimensions() %7B
 %0A   
@@ -3575,35 +3575,41 @@
 %0A        return 
-thi
+dimension
 s;%0A    %7D%0A%0A    @O

improve readability (order of methods)