// License: GPL. For details, see LICENSE file.
package indoor_sweepline;

import java.util.List;
import java.util.Vector;

import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;

public class ModelGeography {

    public ModelGeography(DataSet dataSet, LatLon center) {
        beamsGeography = new Vector<>();

        this.dataSet = dataSet;
        this.center = center;

        wayPool = new Vector<>();
        wayPoolCount = 0;
        nodePool = new Vector<>();
        nodePoolCount = 0;

        nodes = null;
        multipolygon = null;
        members = null;
    }

    private Vector<BeamGeography> beamsGeography;

    private DataSet dataSet;
    private LatLon center;

    private Vector<Way> wayPool;
    private int wayPoolCount;
    private Vector<Node> nodePool;
    private int nodePoolCount;

    private Vector<Node> nodes;
    private Relation multipolygon;
    private Vector<RelationMember> members;

    public void appendNode(Node node) {
        nodes.add(node);
    }

    public DataSet getDataSet() {
        return dataSet;
    }

    public BeamGeography beamAt(int i) {
        return beamsGeography.elementAt(i);
    }

    public void startGeographyBuild(Vector<Beam> beams, Vector<Strip> strips) {
        if (beamsGeography.size() < beams.size())
            beamsGeography.setSize(beams.size());

        double offset = 0;
        for (int i = 0; i < beams.size(); ++i) {
            if (beamsGeography.elementAt(i) == null)
                beamsGeography.setElementAt(new BeamGeography(dataSet, this), i);
            beamsGeography.elementAt(i).adjustNodes(new LatLon(center.lat(), addMetersToLon(center, offset)),
                    beams.elementAt(i).getBeamParts(), beams.elementAt(i).getBeamOffset());

            if (i < strips.size())
                offset += strips.elementAt(i).width;
        }

        nodePoolCount = 0;
        wayPoolCount = 0;

        members = new Vector<>();
        if (multipolygon != null)
            multipolygon.setMembers(members);
    }

    public void startWay() {
        nodes = new Vector<>();
    }

    public void finishWay(Strip strip, int partIndex, boolean isOuter, String level) {
        if (!nodes.isEmpty()) {
            CorridorPart part = strip.partAt(partIndex);
            strip.geographyAt(partIndex).appendNodes(part.getType(), part.getSide(), level,
                    nodes.elementAt(nodes.size()-1), nodes.elementAt(0), this);
            nodes.add(nodes.elementAt(0));
        }
        assignNds(nodes);
        members.add(new RelationMember(isOuter ? "outer" : "inner", wayPool.elementAt(wayPoolCount)));
        ++wayPoolCount;
    }

    public void appendCorridorPart(CorridorPart part, CorridorGeography partGeography, int beamIndex, int partIndex,
            String level) {
        if (!nodes.isEmpty())
            partGeography.appendNodes(part.getType(), part.getSide(), level,
                    nodes.elementAt(nodes.size()-1),
                    beamsGeography.elementAt(beamIndex).coorAt(partIndex), this);
    }

    public void appendUturnNode(Strip strip, int partIndex, int stripIndex, int beamNodeIndex, boolean toTheLeft,
            String level) {
        if (toTheLeft)
            assignCoor(addMeterOffset(beamsGeography.elementAt(stripIndex + 1).coorAt(beamNodeIndex),
                    0, -strip.width / 2.));
        else
            assignCoor(addMeterOffset(beamsGeography.elementAt(stripIndex).coorAt(beamNodeIndex),
                    0, strip.width / 2.));

        if (!nodes.isEmpty()) {
            CorridorPart part = strip.partAt(partIndex);
            strip.geographyAt(partIndex).appendNodes(part.getType(), part.getSide(), level,
                    nodes.elementAt(nodes.size()-1), nodePool.elementAt(nodePoolCount), this);
        }
        nodes.add(nodePool.elementAt(nodePoolCount));
        ++nodePoolCount;
    }

    public void finishGeographyBuild(IndoorSweeplineModel.Type type, String level) {
        for (int i = nodePoolCount; i < nodePool.size(); ++i) {
            nodePool.elementAt(i).setDeleted(true);
        }
        nodePool.setSize(nodePoolCount);

        for (int i = wayPoolCount; i < wayPool.size(); ++i) {
            wayPool.elementAt(i).setDeleted(true);
        }
        wayPool.setSize(wayPoolCount);

        adjustMultipolygonRelation(type, level);
    }

    private static LatLon addMeterOffset(LatLon latLon, double south, double east) {
        double scale = Math.cos(latLon.lat() * (Math.PI/180.));
        return new LatLon(latLon.lat() - south *(360./4e7), latLon.lon() + east / scale *(360./4e7));
    }

    private static double addMetersToLon(LatLon latLon, double east) {
        double scale = Math.cos(latLon.lat() * (Math.PI/180.));
        return latLon.lon() + east / scale *(360./4e7);
    }

    private void assignCoor(LatLon latLon) {
        if (nodePoolCount < nodePool.size())
            nodePool.elementAt(nodePoolCount).setCoor(latLon);
        else {
            Node node = new Node(latLon);
            dataSet.addPrimitive(node);
            nodePool.add(node);
        }
    }

    private void assignNds(List<Node> nodes) {
        if (wayPoolCount < wayPool.size())
            wayPool.elementAt(wayPoolCount).setNodes(nodes);
        else {
            Way way = new Way();
            way.setNodes(nodes);
            dataSet.addPrimitive(way);
            wayPool.add(way);
        }
    }

    private static void addPolygonTags(IndoorSweeplineModel.Type type, String level, OsmPrimitive obj) {
        if (type == IndoorSweeplineModel.Type.PLATFORM) {
            obj.put("railway", "platform");
            obj.put("public_transport", "platform");
            obj.put("area", "yes");
            obj.put("level", level);
        } else {
            obj.put("highway", "pedestrian");
            obj.put("indoor", "corridor");
            obj.put("area", "yes");
            obj.put("level", level);
        }
    }

    private void adjustMultipolygonRelation(IndoorSweeplineModel.Type type, String level) {
        if (members.size() > 1) {
            if (!wayPool.isEmpty())
                wayPool.elementAt(0).removeAll();

            if (multipolygon == null) {
                multipolygon = new Relation();
                dataSet.addPrimitive(multipolygon);
            }

            multipolygon.removeAll();
            multipolygon.put("type", "multipolygon");
            addPolygonTags(type, level, multipolygon);

            multipolygon.setMembers(members);
        } else {
            if (multipolygon != null) {
                multipolygon.setDeleted(true);
                multipolygon = null;
            }

            if (wayPool.size() == 1) {
                wayPool.elementAt(0).removeAll();
                addPolygonTags(type, level, wayPool.elementAt(0));
            }
        }
    }
}
