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

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.controler.events.IterationEndsEvent;
import org.matsim.core.controler.events.IterationStartsEvent;
import org.matsim.core.controler.listener.IterationEndsListener;
import org.matsim.core.controler.listener.IterationStartsListener;
import org.matsim.core.utils.geometry.CoordinateTransformation;

import amodeus.amodeus.data.ReferenceFrame;
import ch.ethz.idsc.tensor.Tensor;
import ch.ethz.idsc.tensor.alg.TensorMap;
import ch.ethz.idsc.tensor.alg.Transpose;
import ch.ethz.idsc.tensor.red.Median;

public class MatsimAmodeusDatabase implements IterationStartsListener, IterationEndsListener {

    public static MatsimAmodeusDatabase initialize(Network network, ReferenceFrame referenceFrame) {
        CoordinateTransformation coords_toWGS84 = referenceFrame.coords_toWGS84();
        List<OsmLink> osmLinks = network.getLinks().values().stream().sorted(Comparator.comparing(link -> link.getId().toString())).map(link -> //
        new OsmLink(link, //
                coords_toWGS84.transform(link.getFromNode().getCoord()), //
                coords_toWGS84.transform(link.getToNode().getCoord()) //
        )).collect(Collectors.toList());
        return new MatsimAmodeusDatabase(referenceFrame, osmLinks);
    }

    /** rapid lookup from MATSIM side */
    private final Map<Integer, OsmLink> integerOsmLinkMap;
    public final ReferenceFrame referenceFrame;

    /** rapid lookup from Viewer */
    private final List<OsmLink> osmLinks;

    private Integer iteration;

    private MatsimAmodeusDatabase( //
            ReferenceFrame referenceFrame, //
            List<OsmLink> osmLinks) {
        this.referenceFrame = referenceFrame;
        this.osmLinks = osmLinks;
        integerOsmLinkMap = this.osmLinks.stream().collect(Collectors.toMap(osmLink -> osmLink.link.getId().index(), Function.identity()));
    }

    public OsmLink getOsmLink(Link link) {
        return getOsmLink(link.getId().index());
    }

    public OsmLink getOsmLink(int index) {
        return integerOsmLinkMap.get(index);
    }

    public Collection<OsmLink> getOsmLinks() {
        return Collections.unmodifiableCollection(osmLinks);
    }

    public Coord getCenter() {
        Tensor points = Tensor.of(getOsmLinks().stream() //
                .map(osmLink -> osmLink.getAt(.5)) //
                .map(TensorCoords::toTensor));
        // Tensor mean = Mean.of(points); // <- mean is fast but doesn't produce as good estimate as median
        Tensor median = TensorMap.of(Median::of, Transpose.of(points), 1);
        return TensorCoords.toCoord(median);
    }

    public int getOsmLinksSize() {
        return osmLinks.size();
    }

    void setIteration(Integer iteration) {
        this.iteration = iteration;
    }

    public int getIteration() {
        return iteration;
    }

    @Override
    public void notifyIterationEnds(IterationEndsEvent event) {
        setIteration(null);
    }

    @Override
    public void notifyIterationStarts(IterationStartsEvent event) {
        setIteration(event.getIteration());
    }
}