/*
 * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package industrial;

/*
 Todo:
 Add emergency button
 Add help text ?
 */
//import com.sun.javafx.perf.PerformanceTracker;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.geometry.Orientation;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.transform.Scale;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Industrial extends Application {

    Model model = new Model();
    Controller controller;
    Timeline controllerTimeline;

    Pump pump;

    Tank tank;

    Slider rateP, rateA, rateB;

    Slider highWater, lowWater;

    Valve valveA, valveB;

    Pipe pipeIn;
    Pipe pipeAIn, pipeAOut;
    Pipe pipeBIn, pipeBOut;

    Text fps;

    Button chartFlowButton;
    Button chartFillButton;

    Group flowChartGroup;
    Group chartFillGroup;
    Group chartFlowGroup;
    Group helpGroup;

// FIXME: Needs public performance tracker API else remove this FPS feature
//    PerformanceTracker tracker;

    static final int layoutSizeX = 1280;
    static final int layoutSizeY = 720;

    static final int chartWidth = 800;
    static final int chartHeight = 500;

    static final int historyPointsToKeep = 60;
    static final int historyInterval = 1;

    Scale rootTransform = new Scale(1.0, 1.0, 0.0, 0.0);

    @Override
    public void start(Stage primaryStage) {

        Label title = new Label("Primary Holding Tank");
        title.setFont(new Font(40));
        title.setTranslateX(430);
        title.setTranslateY(20);

        //****************************
        pipeIn = new Pipe(new Path(
                new MoveTo(0, 550),
                new LineTo(440, 550)));

        pump = new Pump(model);
        Group pumpGroup = pump.getGroup();
        pumpGroup.setTranslateX(110);
        pumpGroup.setTranslateY(440);
        pumpGroup.setScaleX(.5);
        pumpGroup.setScaleY(.5);

        Label nameP = new Label("Feed Pump");
        nameP.setFont(new Font(22));
        nameP.setTranslateX(120);
        nameP.setTranslateY(450);

        Label flowRateP = new Label("Flow Rate:");
        flowRateP.setTranslateX(75);
        flowRateP.setTranslateY(635);

        rateP = new Slider(0.0, 10.0, 3);
        rateP.setShowTickLabels(true);
        rateP.setShowTickMarks(true);
        rateP.setMajorTickUnit(1.0);
        rateP.setBlockIncrement(1.0f);
        rateP.setSnapToTicks(true);
        rateP.setTranslateX(150);
        rateP.setTranslateY(630);

        //****************************
        tank = new Tank(400, 580);
        Group tankGroup = tank.getGroup();
        tankGroup.setTranslateX(440);
        tankGroup.setTranslateY(100);

        //****************************
        pipeAIn = new Pipe(new Path(
                new MoveTo(730, 590),
                new LineTo(1040, 590)));

        pipeAOut = new Pipe(new Path(
                new MoveTo(1135, 590),
                new LineTo(layoutSizeX, 590)));

        valveA = new Valve();
        Group valveAGroup = valveA.getGroup();
        valveAGroup.setScaleX(.5);
        valveAGroup.setScaleY(.5);
        valveAGroup.setTranslateX(1010);
        valveAGroup.setTranslateY(520);

        Label nameA = new Label("Discharge A");
        nameA.setFont(new Font(22));
        nameA.setTranslateX(1030);
        nameA.setTranslateY(500);

        Label flowRateA = new Label("Flow Rate:");
        flowRateA.setTranslateX(950);
        flowRateA.setTranslateY(635);

        rateA = new Slider(0.0, 5.0, 3);
        rateA.setShowTickLabels(true);
        rateA.setShowTickMarks(true);
        rateA.setMajorTickUnit(1.0);
        rateA.setBlockIncrement(1.0f);
        rateA.setSnapToTicks(true);
        rateA.setTranslateX(1025);
        rateA.setTranslateY(630);

        //****************************
        pipeBIn = new Pipe(new Path(
                new MoveTo(730, 390),
                new LineTo(1040, 390)));

        pipeBOut = new Pipe(new Path(
                new MoveTo(1135, 390),
                new LineTo(layoutSizeX, 390)));

        valveB = new Valve();
        Group valveBGroup = valveB.getGroup();
        valveBGroup.setScaleX(.5);
        valveBGroup.setScaleY(.5);
        valveBGroup.setTranslateX(1010);
        valveBGroup.setTranslateY(320);

        Label nameB = new Label("Discharge B");
        nameB.setFont(new Font(22));
        nameB.setTranslateX(1030);
        nameB.setTranslateY(300);

        Label flowRateB = new Label("Flow Rate:");
        flowRateB.setTranslateX(950);
        flowRateB.setTranslateY(435);

        rateB = new Slider(0.0, 5.0, 3);
        rateB.setShowTickLabels(true);
        rateB.setShowTickMarks(true);
        rateB.setMajorTickUnit(1.0);
        rateB.setBlockIncrement(1.0f);
        rateB.setMinorTickCount(4);
        rateP.setSnapToTicks(true);
        rateB.setTranslateX(1025);
        rateB.setTranslateY(430);

        //****************************
// FIXME: Needs public performance tracker API else remove this FPS feature
//        fps = new Text("??? fps");
//        fps.setFont(new Font(25));
//        fps.setLayoutX(10);
//        fps.setLayoutY(40);

        //****************************
        lowWater = new Slider(0, 90, 10);
        lowWater.setShowTickLabels(true);
        lowWater.setShowTickMarks(true);
        lowWater.setMajorTickUnit(10.0);
        lowWater.setBlockIncrement(1.0f);
        lowWater.setMinorTickCount(0);
        lowWater.setSnapToTicks(false);
        lowWater.setOrientation(Orientation.VERTICAL);
        lowWater.setTranslateX(390);
        lowWater.setTranslateY(150);
        lowWater.setPrefHeight(535);

        highWater = new Slider(10, 100, 10);
        highWater.setShowTickLabels(true);
        highWater.setShowTickMarks(true);
        highWater.setMajorTickUnit(10.0);
        highWater.setBlockIncrement(1.0f);
        highWater.setMinorTickCount(0);
        highWater.setSnapToTicks(false);
        highWater.setOrientation(Orientation.VERTICAL);
        highWater.setTranslateX(875);
        highWater.setTranslateY(95);
        highWater.setPrefHeight(535);

        //****************************

        Rectangle chartFlowBG = new Rectangle();
        chartFlowBG.setX(0);
        chartFlowBG.setY(0);
        chartFlowBG.setWidth(chartWidth);
        chartFlowBG.setHeight(chartHeight);
        chartFlowBG.setFill(Color.ANTIQUEWHITE);
        chartFlowBG.setOpacity(0.80);

        final NumberAxis xAxisFlow = new NumberAxis();
        final NumberAxis yAxisFlow = new NumberAxis();

        yAxisFlow.setLabel("Flow Rate");
        yAxisFlow.setAutoRanging(false);
        yAxisFlow.setLowerBound(0);
        yAxisFlow.setUpperBound(10);

        xAxisFlow.setLabel("Time (sec)");
        xAxisFlow.setAutoRanging(false);
        xAxisFlow.setLowerBound(0);
        xAxisFlow.setUpperBound(historyPointsToKeep * historyInterval);

        LineChart lineChartFlow = new LineChart(xAxisFlow, yAxisFlow);
        lineChartFlow.setTitle("Flow History");
        lineChartFlow.setPrefSize(chartWidth, chartHeight);
        lineChartFlow.setAnimated(false);
        lineChartFlow.getData().add(model.getFlowRateHistory(Model.PUMP));
        lineChartFlow.getData().add(model.getFlowRateHistory(Model.VALVE_A));
        lineChartFlow.getData().add(model.getFlowRateHistory(Model.VALVE_B));
        lineChartFlow.setCreateSymbols(false);

        chartFlowGroup = new Group(
                chartFlowBG,
                lineChartFlow
        );

        chartFlowGroup.setVisible(false);
        chartFlowGroup.setTranslateX(200);
        chartFlowGroup.setTranslateY(80);

        chartFlowButton = new Button("Flow History");
        chartFlowButton.setFont(new Font(18));
        chartFlowButton.setTranslateX(30.0);
        chartFlowButton.setTranslateY(80.0);
        chartFlowButton.setOnAction((ActionEvent event) -> {
            boolean visible = !chartFlowGroup.isVisible();
            chartFlowGroup.setVisible(visible);
            if (visible) {
                chartFillGroup.setVisible(false);
            }
        });

        //****************************

        Rectangle chartFillBG = new Rectangle();
        chartFillBG.setX(0);
        chartFillBG.setY(0);
        chartFillBG.setWidth(chartWidth);
        chartFillBG.setHeight(chartHeight);
        chartFillBG.setFill(Color.ANTIQUEWHITE);
        chartFillBG.setOpacity(0.80);

        final NumberAxis xAxisFill = new NumberAxis();
        final NumberAxis yAxisFill = new NumberAxis();

        yAxisFill.setLabel("Fill Percent");
        yAxisFill.setAutoRanging(false);
        yAxisFill.setLowerBound(0);
        yAxisFill.setUpperBound(100);

        xAxisFill.setLabel("Time (sec)");
        xAxisFill.setAutoRanging(false);
        xAxisFill.setLowerBound(0);
        xAxisFill.setUpperBound(historyPointsToKeep * historyInterval);

        LineChart lineChartFill = new LineChart(xAxisFill, yAxisFill);
        lineChartFill.setTitle("Fill History");
        lineChartFill.setPrefSize(chartWidth, chartHeight);
        lineChartFill.setAnimated(false);
        lineChartFill.getData().add(model.getFillPercentHistory());
        lineChartFill.setCreateSymbols(false);

        model.getFillPercentHistory().getData().addListener((ListChangeListener.Change c) -> {
            XYChart.Data get = (XYChart.Data) model.getFillPercentHistory().getData().get(0);
            if (get != null) {

                long low = (long) get.getXValue();
                long high = low + historyPointsToKeep * historyInterval;
                xAxisFill.setLowerBound(low);
                xAxisFill.setUpperBound(high);

                // a bit of a cheat, but these are in lock step
                // and it saves us a listener
                xAxisFlow.setLowerBound(low);
                xAxisFlow.setUpperBound(high);
            }
        });

        chartFillGroup = new Group(
                chartFillBG,
                lineChartFill
        );

        chartFillGroup.setVisible(false);
        chartFillGroup.setTranslateX(200);
        chartFillGroup.setTranslateY(80);

        chartFillButton = new Button("Fill History");
        chartFillButton.setFont(new Font(18));
        chartFillButton.setTranslateX(30.0);
        chartFillButton.setTranslateY(120.0);
        chartFillButton.setOnAction((ActionEvent event) -> {
            boolean visible = !chartFillGroup.isVisible();
            chartFillGroup.setVisible(visible);
            if (visible) {
                chartFlowGroup.setVisible(false);
            }
        });

        //****************************

        helpGroup = Help.createHelp(chartWidth, chartHeight);
        helpGroup.setVisible(false);
        helpGroup.setTranslateX(200);
        helpGroup.setTranslateY(80);

        Button helpButton = new Button("Help");
        helpButton.setFont(new Font(18));
        helpButton.setTranslateX(layoutSizeX - 100);
        helpButton.setTranslateY(80.0);
        helpButton.setOnAction((ActionEvent event) -> {
            boolean visible = !helpGroup.isVisible();
            helpGroup.setVisible(visible);
        });

        //****************************

        Rectangle backdrop = new Rectangle(0, 0, layoutSizeX, layoutSizeY);
        backdrop.setFill(Color.OLDLACE);

        //****************************
        Group root = new Group(
                backdrop,
                pipeIn.getGroup(),
                pumpGroup,
                nameP, flowRateP, rateP,
                pipeAIn.getGroup(),
                pipeBIn.getGroup(),
                tankGroup,
                pipeAOut.getGroup(),
                valveAGroup,
                nameA, flowRateA, rateA,
                pipeBOut.getGroup(),
                valveBGroup,
                nameB, flowRateB, rateB,
                lowWater, highWater,
                chartFlowButton, chartFlowGroup,
                chartFillButton, chartFillGroup,
                helpButton, helpGroup,
                title
        );

        root.getTransforms().add(rootTransform);

        Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
        System.out.println("Screen size is " + primaryScreenBounds);
        Scene scene = new Scene(root, layoutSizeX, layoutSizeY);

// FIXME: Needs public performance tracker API else remove this FPS feature
//        tracker = PerformanceTracker.getSceneTracker(scene);

        controller = new Controller(model /*, tracker */);

//        model.getFPSProperty().addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
//            fps.setText("" + newValue.intValue() + " fps");
//        });

        rateP.valueProperty().bindBidirectional(model.getValveFlowRateProperty(Model.PUMP));
        rateA.valueProperty().bindBidirectional(model.getValveFlowRateProperty(Model.VALVE_A));
        rateB.valueProperty().bindBidirectional(model.getValveFlowRateProperty(Model.VALVE_B));

        tank.getFillPercentageProperty().bind(model.getTankFillPercentProperty());
        tank.getLowWaterPercentageProperty().bind(model.getTankLowWaterPercentProperty());
        tank.getHighWaterPercentageProperty().bind(model.getTankHighWaterPercentProperty());

        model.getValveFlowRateProperty(Model.PUMP).addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
            double lowThreshold = 0.5;
            double oldv = oldValue.doubleValue();
            double newv = newValue.doubleValue();
            boolean active = pump.getPumpActive();
            if (newv < lowThreshold && active) {
                pump.setPumpActive(false);
                pump.setPumpRatePercent(0.0);
                pipeIn.setFlow(false);
            } else {
                double ratePercent = newv / model.getValveFlowRateProperty(Model.PUMP).getMax();
                if (!active) {
                    pump.setPumpActive(true);
                    pipeIn.setFlow(true);
                }
                pump.setPumpRatePercent(ratePercent);
            }
        });

        model.getValveFlowRateProperty(Model.VALVE_A).addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
            double lowThreshold = 0.5;
            double oldv = oldValue.doubleValue();
            double newv = newValue.doubleValue();
            if (oldv < lowThreshold && newv > lowThreshold) {
                pipeAOut.setFilled(true);
                pipeAIn.setFlow(true);
            } else if (newv <= lowThreshold && oldv > lowThreshold) {
                pipeAOut.setFilled(false);
                pipeAIn.setFlow(false);
            }
        });

        model.getValveFlowRateProperty(Model.VALVE_B).addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
            double lowThreshold = 0.5;
            double oldv = oldValue.doubleValue();
            double newv = newValue.doubleValue();
            if (oldv < lowThreshold && newv > lowThreshold) {
                pipeBOut.setFilled(true);
                pipeBIn.setFlow(true);
            } else if (newv <= lowThreshold && oldv > lowThreshold) {
                pipeBOut.setFilled(false);
                pipeBIn.setFlow(false);
            }
        });

        lowWater.valueProperty().bindBidirectional(model.getTankLowWaterPercentProperty());
        highWater.valueProperty().bindBidirectional(model.getTankHighWaterPercentProperty());

        scene.widthProperty().addListener((ObservableValue<? extends Number> observableValue, Number oldSceneWidth, Number newSceneWidth) -> {
            double scale = (double) newSceneWidth / layoutSizeX;
            rootTransform.setX(scale);
        });
        scene.heightProperty().addListener((ObservableValue<? extends Number> observableValue, Number oldSceneHeight, Number newSceneHeight) -> {
            double scale = (double) newSceneHeight / layoutSizeY;
            rootTransform.setY(scale);
        });

        primaryStage.setTitle("Industrial Demo");
        primaryStage.setScene(scene);
        primaryStage.show();

        //this drives the Controller polling.
        final KeyFrame kf = new KeyFrame(Duration.millis(50), controller);
        controllerTimeline = new Timeline(kf);
        controllerTimeline.setCycleCount(Timeline.INDEFINITE);
        controllerTimeline.play();
    }

}
