// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.turnlanes.gui;

import static java.lang.Math.sqrt;
import static org.openstreetmap.josm.plugins.turnlanes.gui.GuiUtil.locs;
import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.coor.ILatLon;
import org.openstreetmap.josm.data.projection.ProjectionRegistry;
import org.openstreetmap.josm.plugins.turnlanes.model.Junction;
import org.openstreetmap.josm.plugins.turnlanes.model.Lane;
import org.openstreetmap.josm.plugins.turnlanes.model.ModelContainer;
import org.openstreetmap.josm.plugins.turnlanes.model.Road;

class GuiContainer {
    static final Color RED = new Color(234, 66, 108);
    static final Color GREEN = new Color(66, 234, 108);

    private static final GuiContainer EMPTY = new GuiContainer(ModelContainer.empty());

    public static GuiContainer empty() {
        return EMPTY;
    }

    private final ModelContainer mc;

    private final Point2D translation;
    /**
     * Meters per pixel.
     */
    private final double mpp;
    private final double scale;
    private final double laneWidth;

    private final Map<Junction, JunctionGui> junctions = new HashMap<>();
    private final Map<Road, RoadGui> roads = new HashMap<>();

    private final Stroke connectionStroke;

    GuiContainer(ModelContainer mc) {
        final Point2D origin = avgOrigin(locs(mc.getPrimaryJunctions()));

        final ILatLon originCoor = ProjectionRegistry.getProjection().eastNorth2latlon(new EastNorth(origin.getX(), origin.getY()));
        final ILatLon relCoor = ProjectionRegistry.getProjection().eastNorth2latlon(
                new EastNorth(origin.getX() + 1, origin.getY() + 1));

        // meters per source unit
        final double mpsu = relCoor.greatCircleDistance(originCoor) / sqrt(2);

        this.mc = mc;
        this.translation = new Point2D.Double(-origin.getX(), -origin.getY());
        this.mpp = 0.5;
        this.scale = mpsu / mpp;

        this.laneWidth = 1.0 / mpp;

        this.connectionStroke = new BasicStroke((float) (laneWidth / 5), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);

        for (Junction j : mc.getPrimaryJunctions()) {
            getGui(j);
        }
    }

    private static Point2D avgOrigin(List<Point2D> locs) {
        if (locs.isEmpty()) {
            return new Point2D.Double(0, 0);
        }

        double x = 0;
        double y = 0;

        for (Point2D l : locs) {
            x += l.getX();
            y += l.getY();
        }

        return new Point2D.Double(x / locs.size(), y / locs.size());
    }

    public JunctionGui getGui(Junction j) {
        final JunctionGui existing = junctions.get(j);
        if (existing != null) {
            return existing;
        }

        return new JunctionGui(this, j);
    }

    void register(JunctionGui j) {
        if (junctions.put(j.getModel(), j) != null) {
            throw new IllegalStateException();
        }
    }

    public RoadGui getGui(Road r) {
        final RoadGui gui = roads.get(r);

        if (gui == null) {
            final RoadGui newGui = new RoadGui(this, r);
            roads.put(r, newGui);
            return newGui;
        }

        return gui;
    }

    Point2D translateAndScale(Point2D loc) {
        return new Point2D.Double((loc.getX() + translation.getX()) * scale, (loc.getY() + translation.getY()) * scale);
    }

    /**
     * Returns meters per pixel
     * @return meters per pixel
     */
    public double getMpp() {
        return mpp;
    }

    public double getScale() {
        return scale;
    }

    public double getLaneWidth() {
        return laneWidth;
    }

    public Stroke getConnectionStroke() {
        return connectionStroke;
    }

    public LaneGui getGui(Lane lane) {
        final RoadGui roadGui = roads.get(lane.getRoad());

        for (LaneGui l : roadGui.getLanes()) {
            if (l.getModel().equals(lane)) {
                return l;
            }
        }

        throw new IllegalArgumentException(tr("No such lane."));
    }

    public ModelContainer getModel() {
        return mc;
    }

    public Rectangle2D getBounds() {
        if (isEmpty()) {
            return new Rectangle2D.Double(-1, -1, 2, 2);
        }

        final List<Junction> primaries = new ArrayList<>(mc.getPrimaryJunctions());
        final List<Double> top = new ArrayList<>();
        final List<Double> left = new ArrayList<>();
        final List<Double> right = new ArrayList<>();
        final List<Double> bottom = new ArrayList<>();

        for (Junction j : primaries) {
            final JunctionGui g = getGui(j);
            final Rectangle2D b = g.getBounds();

            top.add(b.getMinY());
            left.add(b.getMinX());
            right.add(b.getMaxX());
            bottom.add(b.getMaxY());
        }

        final double t = Collections.min(top);
        final double l = Collections.min(left);
        final double r = Collections.max(right);
        final double b = Collections.max(bottom);

        return new Rectangle2D.Double(l, t, r - l, b - t);
    }

    public GuiContainer recalculate() {
        return new GuiContainer(mc.recalculate());
    }

    public Iterable<RoadGui> getRoads() {
        return roads.values();
    }

    public Iterable<JunctionGui> getJunctions() {
        return junctions.values();
    }

    public boolean isEmpty() {
        return mc.isEmpty();
    }
}
