/*
 * Copyright (c) 2017 by Gerrit Grunwald
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.point85.tilesfx.skins;

import static org.point85.tilesfx.tools.Helper.enableNode;

import java.util.List;
import java.util.Locale;

import org.point85.tilesfx.Tile;
import org.point85.tilesfx.chart.ChartData;
import org.point85.tilesfx.events.ChartDataEventListener;
import org.point85.tilesfx.fonts.Fonts;
import org.point85.tilesfx.tools.Helper;

import javafx.beans.InvalidationListener;
import javafx.collections.ListChangeListener;
import javafx.geometry.VPos;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;


public class RadialPercentageTileSkin extends TileSkin {
    private static final double                        ANGLE_RANGE = 360;
    private              double                        size;
    private              double                        chartSize;
    private              Arc                           barBackground;
    private              Arc                           bar;
    private              Arc                           proportionBar;
    private              Line                          separator;
    private              Text                          titleText;
    private              Text                          text;
    private              Text                          percentageValueText;
    private              Text                          percentageUnitText;
    private              TextFlow                      percentageFlow;
    private              Text                          descriptionText;
    private              Text                          unitText;
    private              TextFlow                      valueUnitFlow;
    private              double                        minValue;
    private              double                        range;
    private              double                        angleStep;
    private              double                        referenceValue;
    private              String                        formatString;
    private              Locale                        locale;
    private              double                        sum;
    private              List<ChartData>               dataList;
    private              ChartDataEventListener        chartEventListener;
    private              ListChangeListener<ChartData> chartDataListener;
    private              InvalidationListener          currentValueListener;


    // ******************** Constructors **************************************
    public RadialPercentageTileSkin(Tile TILE) {
        super(TILE);

        setBar(TILE.getCurrentValue());
    }


    // ******************** Initialization ************************************
    @Override protected void initGraphics() {
        super.initGraphics();

        if (tile.isAutoScale()) tile.calcAutoScale();
        minValue             = tile.getMinValue();
        range                = tile.getRange();
        sectionsVisible      = tile.getSectionsVisible();
        sections             = tile.getSections();
        formatString         = new StringBuilder("%.").append(Integer.toString(tile.getDecimals())).append("f").toString();
        locale               = tile.getLocale();
        dataList             = tile.getChartData();
        sum                  = dataList.stream().mapToDouble(ChartData::getValue).sum();
        angleStep            = ANGLE_RANGE / sum;
        referenceValue       = tile.getReferenceValue() < maxValue ? maxValue : tile.getReferenceValue();

        chartEventListener = e -> setProportionBar();
        tile.getChartData().forEach(chartData -> chartData.addChartDataEventListener(chartEventListener));

        chartDataListener = c -> {
            dataList = tile.getChartData();
            sum      = dataList.stream().mapToDouble(ChartData::getValue).sum();
            setProportionBar();
        };
        currentValueListener = o -> setBar(tile.getCurrentValue());

        titleText = new Text();
        titleText.setFill(tile.getTitleColor());
        enableNode(titleText, !tile.getTitle().isEmpty());

        text = new Text(tile.getText());
        text.setFill(tile.getTextColor());
        enableNode(text, tile.isTextVisible());

        barBackground = new Arc(PREFERRED_WIDTH * 0.5, PREFERRED_HEIGHT * 0.5, PREFERRED_WIDTH * 0.468, PREFERRED_HEIGHT * 0.468, 90, 360);
        barBackground.setType(ArcType.OPEN);
        barBackground.setStroke(tile.getBarBackgroundColor());
        barBackground.setStrokeWidth(PREFERRED_WIDTH * 0.1);
        barBackground.setStrokeLineCap(StrokeLineCap.BUTT);
        barBackground.setFill(null);

        bar = new Arc(PREFERRED_WIDTH * 0.5, PREFERRED_HEIGHT * 0.5, PREFERRED_WIDTH * 0.468, PREFERRED_HEIGHT * 0.468, 90, 0);
        bar.setType(ArcType.OPEN);
        bar.setStroke(tile.getBarColor());
        bar.setStrokeWidth(PREFERRED_WIDTH * 0.1);
        bar.setStrokeLineCap(StrokeLineCap.BUTT);
        bar.setFill(null);

        proportionBar = new Arc(PREFERRED_WIDTH * 0.5, PREFERRED_HEIGHT * 0.5, PREFERRED_WIDTH * 0.468, PREFERRED_HEIGHT * 0.468, 90, 0);
        proportionBar.setType(ArcType.OPEN);
        proportionBar.setStroke(tile.getBarColor());
        proportionBar.setStrokeWidth(PREFERRED_WIDTH * 0.015);
        proportionBar.setStrokeLineCap(StrokeLineCap.BUTT);
        proportionBar.setFill(null);

        separator = new Line(PREFERRED_WIDTH * 0.5, 1, PREFERRED_WIDTH * 0.5, 0.16667 * PREFERRED_HEIGHT);
        separator.setStroke(tile.getBackgroundColor());
        separator.setFill(Color.TRANSPARENT);

        percentageValueText = new Text(String.format(locale, formatString, tile.getCurrentValue()));
        percentageValueText.setFont(Fonts.latoRegular(PREFERRED_WIDTH * 0.27333));
        percentageValueText.setFill(tile.getValueColor());
        percentageValueText.setTextOrigin(VPos.CENTER);

        percentageUnitText = new Text(tile.getUnit());
        percentageUnitText = new Text("\u0025");
        percentageUnitText.setFont(Fonts.latoLight(PREFERRED_WIDTH * 0.08));
        percentageUnitText.setFill(tile.getUnitColor());

        percentageFlow = new TextFlow(percentageValueText, percentageUnitText);
        percentageFlow.setTextAlignment(TextAlignment.CENTER);

        descriptionText = new Text(String.format(locale, formatString, tile.getCurrentValue()));
        descriptionText.setFont(Fonts.latoRegular(PREFERRED_WIDTH * 0.27333));
        descriptionText.setFill(tile.getValueColor());
        descriptionText.setTextOrigin(VPos.CENTER);
        enableNode(descriptionText, tile.isValueVisible());

        unitText = new Text(tile.getUnit());
        unitText = new Text("\u0025");
        unitText.setFont(Fonts.latoLight(PREFERRED_WIDTH * 0.08));
        unitText.setFill(tile.getUnitColor());
        enableNode(unitText, !tile.getUnit().isEmpty());

        valueUnitFlow = new TextFlow(descriptionText, unitText);
        valueUnitFlow.setTextAlignment(TextAlignment.CENTER);

        getPane().getChildren().addAll(barBackground, proportionBar, bar, separator, titleText, text, percentageFlow, valueUnitFlow);
    }

    @Override protected void registerListeners() {
        super.registerListeners();
        tile.getChartData().addListener(chartDataListener);
        tile.currentValueProperty().addListener(currentValueListener);
    }


    // ******************** Methods *******************************************
    @Override protected void handleEvents(final String EVENT_TYPE) {
        super.handleEvents(EVENT_TYPE);
        if ("RECALC".equals(EVENT_TYPE)) {
            referenceValue = tile.getReferenceValue() < maxValue ? maxValue : tile.getReferenceValue();
            angleStep      = ANGLE_RANGE / range;
            sum            = dataList.stream().mapToDouble(ChartData::getValue).sum();
            sections       = tile.getSections();
            redraw();
            setBar(tile.getCurrentValue());
        } else if ("VISIBILITY".equals(EVENT_TYPE)) {
            enableNode(titleText, !tile.getTitle().isEmpty());
            enableNode(text, tile.isTextVisible());
            enableNode(unitText, !tile.getUnit().isEmpty());
            enableNode(descriptionText, tile.isValueVisible());
        }
    }

    private void setBar(final double VALUE) {
        if (minValue > 0) {
            bar.setLength((minValue - VALUE) * angleStep);
        } else {
            bar.setLength(-VALUE * angleStep);
        }

        percentageValueText.setText(String.format(locale, formatString, VALUE / sum * 100.0));
        setProportionBar();
    }

    private void setProportionBar() {
        sum = dataList.stream().mapToDouble(ChartData::getValue).sum();
        proportionBar.setLength(-sum * ANGLE_RANGE / referenceValue);
    }

    @Override public void dispose() {
        tile.currentValueProperty().removeListener(currentValueListener);
        tile.getChartData().removeListener(chartDataListener);
        tile.getChartData().forEach(chartData -> chartData.removeChartDataEventListener(chartEventListener));
        super.dispose();
    }


    // ******************** Resizing ******************************************
    @Override protected void resizeStaticText() {
        double maxWidth = width - size * 0.1;
        double fontSize = size * textSize.factor;

        titleText.setFont(Fonts.latoRegular(fontSize));
        if (titleText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(titleText, maxWidth, fontSize); }
        switch(tile.getTitleAlignment()) {
            default    :
            case LEFT  : titleText.relocate(size * 0.05, size * 0.05); break;
            case CENTER: titleText.relocate((width - titleText.getLayoutBounds().getWidth()) * 0.5, size * 0.05); break;
            case RIGHT : titleText.relocate(width - (size * 0.05) - titleText.getLayoutBounds().getWidth(), size * 0.05); break;
        }

        text.setFont(Fonts.latoRegular(fontSize));
        if (text.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(text, maxWidth, fontSize); }
        switch(tile.getTextAlignment()) {
            default    :
            case LEFT  : text.setX(size * 0.05); break;
            case CENTER: text.setX((width - text.getLayoutBounds().getWidth()) * 0.5); break;
            case RIGHT : text.setX(width - (size * 0.05) - text.getLayoutBounds().getWidth()); break;
        }
        text.setY(height - size * 0.05);
    }
    @Override protected void resizeDynamicText() {
        double maxWidth = percentageUnitText.isVisible() ? chartSize * 0.7 : chartSize * 0.8;
        double fontSize = chartSize * 0.2;
        percentageValueText.setFont(Fonts.latoRegular(fontSize));
        if (percentageValueText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(percentageValueText, maxWidth, fontSize); }

        fontSize = chartSize * 0.08;
        percentageUnitText.setFont(Fonts.latoLight(fontSize));
        if (percentageUnitText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(percentageUnitText, maxWidth, fontSize); }

        maxWidth = unitText.isVisible() ? chartSize * 0.3 : chartSize * 0.4;
        fontSize = chartSize * 0.075;
        descriptionText.setFont(Fonts.latoRegular(fontSize));
        if (descriptionText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(descriptionText, maxWidth, fontSize); }

        fontSize = chartSize * 0.04;
        unitText.setFont(Fonts.latoLight(fontSize));
        if (unitText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(unitText, maxWidth, fontSize); }
    }

    @Override protected void resize() {
        super.resize();
        width  = tile.getWidth() - tile.getInsets().getLeft() - tile.getInsets().getRight();
        height = tile.getHeight() - tile.getInsets().getTop() - tile.getInsets().getBottom();
        size   = width < height ? width : height;

        if (width > 0 && height > 0) {
            pane.setMaxSize(width, height);
            pane.setPrefSize(width, height);

            double chartWidth  = contentBounds.getWidth();
            double chartHeight = contentBounds.getHeight();
            chartSize = chartWidth < chartHeight ? chartWidth : chartHeight;

            double y = height * 0.15 + (height * (tile.isTextVisible() ? 0.75 : 0.85) - chartSize) * 0.5;
            //double radius = chartSize * 0.495 - contentBounds.getX();

            double radius = chartSize * 0.4135;

            barBackground.setCenterX(contentCenterX);
            barBackground.setCenterY(contentCenterY);
            barBackground.setRadiusX(radius);
            barBackground.setRadiusY(radius);
            barBackground.setStrokeWidth(chartSize * 0.015);

            proportionBar.setCenterX(contentCenterX);
            proportionBar.setCenterY(contentCenterY);
            proportionBar.setRadiusX(chartSize * 0.456);
            proportionBar.setRadiusY(chartSize * 0.456);
            proportionBar.setStrokeWidth(chartSize * 0.015);

            bar.setCenterX(contentCenterX);
            bar.setCenterY(contentCenterY);
            bar.setRadiusX(radius);
            bar.setRadiusY(radius);
            bar.setStrokeWidth(chartSize * 0.1);

            separator.setStartX(contentCenterX);
            separator.setStartY(y + chartSize * 0.0365);
            separator.setEndX(contentCenterX);
            separator.setEndY(y + chartSize * 0.1365);

            resizeStaticText();
            resizeDynamicText();

            percentageFlow.setPrefWidth(contentBounds.getWidth());
            percentageFlow.relocate(contentBounds.getX(), bar.getCenterY() - chartSize * 0.2);

            valueUnitFlow.setPrefWidth(width * 0.6);
            valueUnitFlow.relocate((width * 0.2), bar.getCenterY() + chartSize * 0.06);
        }
    }

    @Override protected void redraw() {
        super.redraw();
        locale           = tile.getLocale();
        formatString     = new StringBuilder("%.").append(Integer.toString(tile.getDecimals())).append("f").toString();
        sectionsVisible  = tile.getSectionsVisible();

        barBackground.setStroke(tile.getBarBackgroundColor());

        LinearGradient gradient = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE, tile.getGradientStops());

        bar.setStroke(tile.isFillWithGradient() ? gradient : tile.getBarColor());
        proportionBar.setStroke(tile.isFillWithGradient() ? gradient : tile.getBarColor());
        percentageValueText.setFill(tile.getValueColor());
        percentageUnitText.setFill(tile.getUnitColor());
        descriptionText.setFill(tile.getDescriptionColor());
        unitText.setFill(tile.getUnitColor());
        titleText.setFill(tile.getTitleColor());
        text.setFill(tile.getTextColor());
        separator.setStroke(tile.getBackgroundColor());

        titleText.setText(tile.getTitle());
        descriptionText.setText(tile.getDescription());
        text.setText(tile.getText());
        unitText.setText(tile.getUnit());

        referenceValue = tile.getReferenceValue() < maxValue ? maxValue : tile.getReferenceValue();

        resizeStaticText();
        resizeDynamicText();
    }
}