package be.iminds.ilabt.jfed.experimenter_gui.timeline.view;

import be.iminds.ilabt.jfed.experimenter_gui.timeline.util.TimeCoordinateTranslator;
import be.iminds.ilabt.jfed.rspec.model.controller.Timestamp;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Cedric
 */
public class TimelineBase extends Pane implements TimelineComponent {
    private final Rectangle base;

    public final static double TICKMARK_HEIGHT = 45;

    private final double Y_OFFSET = 40;
    private final double BIG_Y = 20;
    private final double SMALL_Y = 15;
    private final double TICK_SPACING = 15;

    //mapping width -> text, so labels can be updated easily
    private final Map<Double, Text> labels = new HashMap<>();

    private final List<Line> ticks = new ArrayList<>();

    private final TimeCoordinateTranslator translator;

    public TimelineBase() {
        translator = TimeCoordinateTranslator.getInstance();

        base = new Rectangle();
        base.getStyleClass().add("base");
        base.heightProperty().bind(heightProperty());
        base.widthProperty().bind(widthProperty());

        this.getChildren().add(base);

        initTicks();

    }

    private void initTicks() {
        getChildren().removeAll(getChildren());
        labels.clear();

        getChildren().add(base);

        Line baseline = new Line();
        baseline.setStartX(0);
        baseline.endXProperty().bind(this.widthProperty());
        baseline.setStartY(Y_OFFSET);
        baseline.setEndY(Y_OFFSET);
        baseline.getStyleClass().add("timelineBase");

        getChildren().add(baseline);

        double currentWidth = 0;

        //Every 5th tick to be a big tick with a label
        int tickCount = 0;
        while (currentWidth <= getWidth()) {

            if (tickCount % 5 == 0) {
                addBigTick(currentWidth);
            } else {
                addSmallTick(currentWidth);
            }

            currentWidth += TICK_SPACING;
            tickCount++;
        }
    }

    private void updateLabels() {
        for (double width : labels.keySet()) {
            Timestamp t = new Timestamp(translator.translateWidthToTime(width));
            labels.get(width).setText(t.toString());
        }
    }

    /**
     * Removes or adds ticks if needed
     */
    private void updateTicks() {
        if (ticks.isEmpty())
            return;

        Line lastTick = ticks.get(ticks.size() - 1);
        /*
            In case of a width increase:
        -------------------------------
            Adding ticks:
            | | | |        |
            | | | |        |
            | | | |        |
            ------- ticks
            ---------------- new width
            -> add ticks until pane.Width - TICK_SPACING < lastTick.X < pane.Width
        */
        while (lastTick.getStartX() < this.getWidth() - TICK_SPACING) {

            double newX = lastTick.getStartX() + TICK_SPACING;
            //New tick should be a big tick?
            if ((newX / TICK_SPACING) % 5 == 0) {
                addBigTick(newX);
            } else {
                addSmallTick(newX);
            }

            lastTick = ticks.get(ticks.size() - 1);
        }
        
        /*
            In case of a width decrease:
        -------------------------------
        */
        while (lastTick.getStartX() > this.getWidth()) {
            ticks.remove(lastTick);
            getChildren().remove(lastTick);
            if (labels.containsKey(lastTick.getStartX())) {
                getChildren().remove(labels.get(lastTick.getStartX()));
                labels.remove(lastTick.getStartX());
            }

            lastTick = ticks.get(ticks.size() - 1);
        }
    }

    /**
     * Adds a big tick and a minor tick (minor = full pane height, slim line)
     * ALso adds a label for the big tick
     */
    private void addBigTick(double currentWidth) {
        Line t = new Line();
        t.setStartX(currentWidth);
        t.setEndX(currentWidth);
        t.setStartY(TICKMARK_HEIGHT);
        t.setEndY(TICKMARK_HEIGHT - BIG_Y);
        t.getStyleClass().add("timetickBig");

        Timestamp ts = new Timestamp(translator.translateWidthToTime(currentWidth));
        Text l = new Text(ts.toString());
        l.getStyleClass().add("timelineLabel");
        l.snapshot(null, null);
        double width = l.getLayoutBounds().getWidth();
        l.setX(currentWidth - width * 0.5);
        l.setY((TICKMARK_HEIGHT - BIG_Y) * 0.5);
        getChildren().add(l);

        labels.put(currentWidth, l);

        Line minor = new Line();
        minor.setStartX(currentWidth);
        minor.setEndX(currentWidth);
        minor.setStartY(TICKMARK_HEIGHT);
        minor.endYProperty().bind(heightProperty());
        minor.getStyleClass().add("timelineLongTick");

        getChildren().addAll(minor, t);
        ticks.add(minor);
        ticks.add(t);
    }

    public void addSmallTick(double currentWidth) {
        Line t = new Line();
        t.setStartX(currentWidth);
        t.setEndX(currentWidth);
        t.setStartY(TICKMARK_HEIGHT);
        t.setEndY(TICKMARK_HEIGHT - SMALL_Y);
        t.getStyleClass().add("timetickSmall");
        ticks.add(t);
        getChildren().add(t);
    }

    @Override
    public void resize(double d, double d2) {
        //for some reason highlighting other components invokes a resize with the same dimensions.. this will ignore those
        if (getWidth() != d || getHeight() != d) {
            super.resize(d, d2);
            updateTicks();

            getStylesheets().remove(TimelineTimeline.class.getResource("Controller.css").toExternalForm());
            getStylesheets().add(TimelineTimeline.class.getResource("Controller.css").toExternalForm());
        }
    }

    @Override
    public void update(UpdateEvent event, Object o) {
        if (event == UpdateEvent.UPDATE_ZOOM) {
            updateLabels();
        }
    }


}
