package whiteout;

import javafx.animation.Timeline;
import javafx.animation.Interpolator;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import java.util.Random;

class Light extends Group {
    public-init var env: Env;

    // These are the array coordinates of the button in the nxn array of buttons.
    var gx: Integer;
    var gy: Integer;
    var selected: Boolean = false;
    var row: Row;
    var model: Model;
    def size1 = env.gameButtonSize;

    // This fades the border around a game button in/out as the mouse enters/leaves.
    var strokeAlpha = 0.0;
    def fade = Timeline {
        keyFrames: [
            at(0s)   { strokeAlpha => 0.0 tween Interpolator.LINEAR }
            at(0.5s) { strokeAlpha => 1.0 tween Interpolator.LINEAR }
        ]
    };

    var view: Rectangle  = Rectangle {
        fill: env.blueGrad
        width: size1
        height: size1
        arcWidth: 10
        arcHeight: 10
        stroke: bind Color {
            red: 0.9
            green: 0.9
            blue: 0.9
            opacity: strokeAlpha
        }
        strokeWidth: 3
        onMouseEntered: function(e) {
            if (not finished) {
                fade.rate = 10.0;
                fade.play();
            }
        }
        onMouseExited: function(e) {
            // This has to happen even if finished to 
            // turn off the border of the cell that got us to a finished state.
            fade.rate = -10.0;
            fade.play();
        }
        onMousePressed: function(e) {
            if (not finished) {
                toggle();
            }
            // Note that toggle() can set 'finished'.
            if (finished) {
                fade.rate = -10.0;
                fade.play();
            }
        }
    };

    init {
        content = [
            Rectangle {
                fill: Color.rgb(0x0,0x0,0x0,.5)
                translateX: 2
                translateY: 2
                width: size1
                height: size1
                arcWidth: 10
                arcHeight: 10
            }
            view
        ];
        translateX = env.gameButtonGap + gx * (size1 + env.gameButtonGap);
        translateY = env.topMargin + gy * (size1 + env.gameButtonGap);
    }  // end init

    function setSelected(t: Boolean ) : Void {
        if (not selected == t) {
            selected = t;
            view.fill = if (selected) Color.WHITE else env.blueGrad;
        }
    }

    function triggerAdjacent() {
        if (sizeof row.lights > gx + 1) {
            row.lights[gx + 1].flip();
        }
        if (0 <= gx - 1) {
            row.lights[gx - 1].flip();
        }
        if (sizeof model.rows > gy + 1) {
            row.model.rows[gy + 1].lights[gx].flip();
        }
        if (0 <= gy - 1) {
            row.model.rows[gy - 1].lights[gx].flip();
        }
    }

    //flip this one and the adjacent ones
    function toggle() : Void {
        setSelected(not selected);
        triggerAdjacent();
        model.moveCount++;
        model.checkFinished();
    }

    //just flip this one
    function flip() {
        setSelected(not selected);
    }
}

class Row extends Group {
    var lights: Light[];
    var model: Model;
}

public class Model extends Group {
    public-init var env: Env;
    public var rows : Row[];
    public var moveCount: Number;
    public var finished: Boolean;
    public var level = 0;
    def generator = new Random();
    init {
        for (i in [0..env.nButtons - 1]) {
            def row = Row {model: this};
            for (j in [0..env.nButtons - 1]) {
                def light = Light{env: env, gx: j, gy: i, row: row, model: this};
                insert light into row.lights;
            }
           insert row into rows;
           row.content = row.lights;
        }
        content = rows;
        randomize();
    }

    public function reset() {
        clear();
        randomize();
    }

    public function clear() {
        for (aRow in rows, aLight in aRow.lights) {
             aLight.setSelected(false);
        }
    }

    public function randomize() {
        // Set two values per level.  Note that since the initial config
        // is created by simulating clicks, then the game should always be solvable.
        // If you change this to just do a bunch of setSelected(true) instead of toggles
        // then, the game won't necessarily be solvable, eg, if only one button is set
        // to start with.
        for (i in [0..(level + 1)]) {
            // Note the use of 4 instead of nButtons.  If we used nButtons and it
            // was much greater than 4, then there is a reduced likelihood
            // that one toggle will overlap another and we frequently will end up with
            // a pattern whose solution is trivial.
            rows[generator.nextInt(4)].lights[generator.nextInt(4)].toggle();
        }
        moveCount = 0;
        finished = false;
    }

    function checkFinished() {
        for (aRow in rows, aLight in aRow.lights) {
             if (aLight.selected) {
                 return
             }
        }
        finished = true;
    }
}