/* amodeus - Copyright (c) 2018, ETH Zurich, Institute for Dynamic Systems and Control */
package amodeus.amodeus.gfx;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

import javax.swing.JCheckBox;
import javax.swing.JTextArea;

import amodeus.amodeus.net.OsmLink;
import amodeus.amodeus.net.SimulationObject;
import amodeus.amodeus.util.gui.GraphicsUtil;
import amodeus.amodeus.util.gui.LazyMouse;
import amodeus.amodeus.util.gui.LazyMouseListener;
import amodeus.amodeus.util.gui.RowPanel;
import amodeus.amodeus.util.nd.NdCenterInterface;
import amodeus.amodeus.util.nd.NdCluster;
import amodeus.amodeus.util.nd.NdEntry;
import amodeus.amodeus.util.nd.NdMap;
import amodeus.amodeus.util.nd.NdTreeMap;
import amodeus.amodeus.view.jmapviewer.interfaces.ICoordinate;
import ch.ethz.idsc.tensor.Tensor;
import ch.ethz.idsc.tensor.Tensors;
import ch.ethz.idsc.tensor.alg.Array;
import ch.ethz.idsc.tensor.sca.ArcTan;

/* package */ class Street {
    final OsmLink osmLink;
    Point p1;
    Point p2;

    public Street(OsmLink osmLink) {
        this.osmLink = osmLink;
    }

    Point midpoint() {
        return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
    }

    Tensor getLocation() {
        Point mid = midpoint();
        return Tensors.vector(mid.x, mid.y);
    }

    double angle() {
        return ArcTan.of(p2.x - p1.x, p2.y - p1.y).number().doubleValue();
    }
}

public class LinkLayer extends ViewerLayer {
    private static final Color LINKCOLOR = new Color(153, 153, 102, 64);
    // ---
    public int linkLimit = 8192;
    private boolean drawLinks;
    private boolean drawLabel;
    private boolean drawCoordinates;
    private final JTextArea jTextArea = new JTextArea(2, 10);
    private int count = 0;
    private ICoordinate lastCoord;

    public LinkLayer(AmodeusComponent amodeusComponent) {
        super(amodeusComponent);
    }

    @Override
    protected void paint(Graphics2D graphics, SimulationObject ref) {
        if (drawLinks) {
            Dimension dimension = amodeusComponent.getSize();
            NdMap<Street> map = new NdTreeMap<>(Array.zeros(2), Tensors.vector(dimension.width, dimension.height), 12, 8);
            // draw links
            drawLinks(map, graphics);
            // draw labels
            if (drawLabel && Objects.nonNull(lastCoord))
                drawLabel(map, graphics);
            else
                jTextArea.setText("");
        }
        // draw location where mouse clicked
        if (drawCoordinates && Objects.nonNull(lastCoord))
            drawLastCoord(graphics);
    }

    /** Draw last coordinate of where mouse clicked onto graphics object
     *
     * @param graphics Graphics2D object on which the last coord will be drawn/highlighted */
    private void drawLastCoord(Graphics2D graphics) {
        Point p = amodeusComponent.getMapPosition(lastCoord.getLat(), lastCoord.getLon());
        if (Objects.nonNull(p)) {
            graphics.setColor(Color.WHITE);
            graphics.fillRect(p.x - 1, p.y - 1, 3, 3);
            graphics.drawString(String.format("[lng=%.6f, lat=%.6f]", lastCoord.getLon(), lastCoord.getLat()), p.x + 10, p.y - 10);
        }
    }

    /** Draw links onto graphics object
     *
     * @param map to find closest streets
     * @param graphics Graphics2D object on which the links are drawn */
    private void drawLinks(NdMap<Street> map, Graphics2D graphics) {
        List<Street> list = new LinkedList<>();
        for (OsmLink osmLink : amodeusComponent.db.getOsmLinks()) {
            Point p1 = amodeusComponent.getMapPosition(osmLink.getCoordFrom());
            if (Objects.nonNull(p1)) {
                Point p2 = amodeusComponent.getMapPositionAlways(osmLink.getCoordTo());
                Street street = new Street(osmLink);
                street.p1 = p1;
                street.p2 = p2;
                list.add(street);
                if (linkLimit < list.size()) {
                    list.clear();
                    break;
                }
            }
        }
        graphics.setColor(LINKCOLOR);
        for (Street street : list) {
            Point p1 = street.p1;
            Point p2 = street.p2;
            if (p1.equals(p2))
                graphics.drawOval(p1.x - 1, p1.y - 1, 3, 3);
            else
                graphics.drawLine(p1.x, p1.y, p2.x, p2.y);
            map.add(street.getLocation(), street);
        }
        count = list.size();
    }

    /** Draw labels onto graphics object
     *
     * @param map to find closest streets
     * @param graphics Graphics2D object on which the labels are to be drawn */
    private void drawLabel(NdMap<Street> map, Graphics2D graphics) {
        final Point point = amodeusComponent.getMapPosition(lastCoord.getLat(), lastCoord.getLon());
        if (Objects.nonNull(point)) {
            Tensor center = Tensors.vector(point.x, point.y);
            NdCluster<Street> cluster = map.buildCluster(NdCenterInterface.euclidean(center), 2);
            graphics.setFont(new Font(Font.DIALOG, Font.PLAIN, 12));
            GraphicsUtil.setQualityHigh(graphics);
            for (NdEntry<Street> entry : cluster.collection()) {
                Street street = entry.value();
                double theta = street.angle();
                Point p1 = street.p1;
                Point p2 = street.p2;
                graphics.setColor(Color.RED);
                graphics.drawLine(p1.x, p1.y, p2.x, p2.y);
                graphics.setColor(Color.WHITE);
                graphics.fillRect(p1.x - 1, p1.y - 1, 3, 3);
                graphics.fillRect(p2.x - 1, p2.y - 1, 3, 3);
                AffineTransform saveAT = graphics.getTransform();
                graphics.rotate(theta, p1.x, p1.y);
                graphics.drawString( //
                        "\u2192 " + street.osmLink.link.getId().toString() + " \u2192", //
                        p1.x + 10, p1.y + 10);
                graphics.setTransform(saveAT);
                jTextArea.setText(street.osmLink.link.getId().toString());
            }
            GraphicsUtil.setQualityDefault(graphics);
        }

    }

    @Override
    protected void hud(Graphics2D graphics, SimulationObject ref) {
        if (drawLinks) {
            if (0 < count)
                amodeusComponent.append("%5d/%5d streets", count, amodeusComponent.db.getOsmLinksSize());
            else
                amodeusComponent.append("too many streets");
            amodeusComponent.appendSeparator();
        }
    }

    public void setDrawLinks(boolean links) {
        drawLinks = links;
        amodeusComponent.repaint();
    }

    public void setDrawLabel(boolean label) {
        drawLabel = label;
        amodeusComponent.repaint();
    }

    public void setDrawCoordinates(boolean coords) {
        drawCoordinates = coords;
        amodeusComponent.repaint();
    }

    @Override
    protected void createPanel(RowPanel rowPanel) {
        {
            final JCheckBox jCheckBox = new JCheckBox("streets");
            jCheckBox.setToolTipText("each link as thin line");
            jCheckBox.setSelected(drawLinks);
            jCheckBox.addActionListener(event -> setDrawLinks(jCheckBox.isSelected()));
            rowPanel.add(jCheckBox);
        }
        {
            final JCheckBox jCheckBox = new JCheckBox("click label");
            jCheckBox.setToolTipText("mouse click labels streets with ID");
            jCheckBox.setSelected(drawLabel);
            jCheckBox.addActionListener(event -> setDrawLabel(jCheckBox.isSelected()));
            rowPanel.add(jCheckBox);
        }
        {
            rowPanel.add(jTextArea);
        }
        {
            final JCheckBox jCheckBox = new JCheckBox("click coordinates");
            jCheckBox.setToolTipText("longitude and latitude of click position");
            jCheckBox.setSelected(drawCoordinates);
            jCheckBox.addActionListener(event -> setDrawCoordinates(jCheckBox.isSelected()));
            rowPanel.add(jCheckBox);
        }
        LazyMouseListener lazyMouseListener = new LazyMouseListener() {
            @Override
            public void lazyClicked(MouseEvent mouseEvent) {
                // set last coordinate
                lastCoord = amodeusComponent.getPosition(mouseEvent.getPoint());
            }
        };
        LazyMouse lazyMouse = new LazyMouse(lazyMouseListener);
        lazyMouse.addListenersTo(amodeusComponent);
    }

    @Override
    public void updateSettings(ViewerSettings settings) {
        settings.drawLinks = drawLinks;
        settings.drawLabel = drawLabel;
        settings.drawCoordinates = drawCoordinates;
    }

    @Override
    public void loadSettings(ViewerSettings settings) {
        setDrawLinks(settings.drawLinks);
        setDrawLabel(settings.drawLabel);
        setDrawCoordinates(settings.drawCoordinates);
    }
}