package org.opentrafficsim.demo.ntm;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.djunits.unit.FrequencyUnit;
import org.djunits.unit.LengthUnit;
import org.djunits.unit.SpeedUnit;
import org.djunits.value.vdouble.scalar.Frequency;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opentrafficsim.core.geometry.OTSLine3D;
import org.opentrafficsim.demo.ntm.NTMNode.TrafficBehaviourType;
/**
 * 
 * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. 
 * BSD-style license. See OpenTrafficSim License.
 * 
 * $LastChangedDate$, @version $Revision$, by $Author$,
 * initial version Sep 11, 2014 
 * @author Alexander Verbraeck
 * @author Guus Tamminga
 */
public class ShapeFileReader
{
    /**
     * @param shapeFileName String; the areas shapefile to read
     * @param centroids Map<String,NTMNode>; the map of centroids
     * @return map of areas with areanr as the key
     * @throws IOException on error
     */
    public static Map readAreas(final String shapeFileName, final Map centroids,
            double scalingFactorDemand) throws IOException
    {
        /*-
        the_geom class com.vividsolutions.jts.geom.MultiPolygon MULTIPOLYGON (((81816.4228569232, ...
        AREANR class java.lang.Long 15127
        NAME class java.lang.String 70 Oostduinen
        CENTROIDNR class java.lang.Long 1
        NAMENR class java.lang.Long 175
        GEMEENTE_N class java.lang.String S Gravenhage
        GEMEENTEVM class java.lang.String sGravenhage
        GEBIEDSNAA class java.lang.String Studiegebied
        REGIO class java.lang.String Den_Haag
        MATCOMPRES class java.lang.String Scheveningen
        DHB class java.lang.Double 70.0
        PARKEERTAR class java.lang.String 
        AREATAG class java.lang.String 
         */
        URL url;
        if (new File(shapeFileName).canRead())
        {
            url = new File(shapeFileName).toURI().toURL();
        }
        else
        {
            url = ShapeFileReader.class.getResource(shapeFileName);
        }
        ShapefileDataStore storeAreas = (ShapefileDataStore) FileDataStoreFinder.getDataStore(url);
        Map areas = new LinkedHashMap<>();
        SimpleFeatureSource featureSourceAreas = storeAreas.getFeatureSource();
        SimpleFeatureCollection featureCollectionAreas = featureSourceAreas.getFeatures();
        SimpleFeatureIterator iterator = featureCollectionAreas.features();
        Long newNr = 100000000L;
        int numberOfAreasWithoutCentroid = 0;
        int numberOfAreasWithCentroid = 0;
        try
        {
            // loop through the areas
            while (iterator.hasNext())
            {
                SimpleFeature feature = iterator.next();
                Geometry geometry = (Geometry) feature.getAttribute("the_geom");
                // String nr = String.valueOf(feature.getAttribute("AREANR"));
                String centroidNr = "C" + String.valueOf(feature.getAttribute("CENTROIDNR"));
                String name = (String) feature.getAttribute("NAME");
                String gemeente = (String) feature.getAttribute("GEMEENTEVM");
                String gebied = (String) feature.getAttribute("GEBIEDSNAA");
                String regio = (String) feature.getAttribute("REGIO");
                // double dhb = (double) feature.getAttribute("DHB");
                /*
                 * String gemeente = "empty"; String gebied = "empty"; String regio = "empty";
                 */
                double dhb = (double) 0.0;
                NTMNode centroidNode = null;
                if (centroids != null)
                {
                    // search for areas within the centroids (from the "points")
                    centroidNode = centroids.get(centroidNr);
                }
                if (centroidNode == null)
                {
                    for (NTMNode node : centroids.values())
                    {
                        Geometry g = new GeometryFactory().createPoint(node.getPoint().getCoordinate());
                        if (geometry.contains(g))
                        {
                            centroidNode = node;
                            centroidNr = node.getId();
                        }
                    }
                }
                if (centroidNode == null)
                {
                    // System.out.println("Centroid with number " + centroidNr + " not found for area " + nr + " (" +
                    // name
                    // + ")");
                    numberOfAreasWithoutCentroid++;
                }
                else
                {
                    if (areas.containsKey(centroidNr))
                    {
                        System.out.println("Area number " + centroidNr + "(" + name + ") already exists. Number not unique!");
                        newNr++;
                        centroidNr = newNr.toString();
                    }
                    double accCritMaxCapStart = 25;
                    double accCritMaxCapEnd = 50;
                    double accCritJam = 100;
                    double increaseDemandByFactor = scalingFactorDemand;
                    ArrayList accCritical = new ArrayList();
                    accCritical.add(accCritMaxCapStart);
                    accCritical.add(accCritMaxCapEnd);
                    accCritical.add(accCritJam);
                    ParametersNTM parametersNTM = new ParametersNTM(accCritical);
                    Area area = new Area(geometry, centroidNr, name, gemeente, gebied, regio, dhb,
                            centroidNode.getPoint().getCoordinate(), TrafficBehaviourType.NTM, new Length(0, LengthUnit.METER),
                            new Speed(0, SpeedUnit.KM_PER_HOUR), increaseDemandByFactor, parametersNTM);
                    areas.put(centroidNr, area);
                    numberOfAreasWithCentroid++;
                }
            }
            if (centroids != null)
            {
                System.out.println("Number of centroids " + centroids.size());
                System.out.println("Number of areas with centroids " + numberOfAreasWithCentroid);
                System.out.println("Number of areas without centroids " + numberOfAreasWithoutCentroid);
            }
        }
        catch (Exception problem)
        {
            problem.printStackTrace();
        }
        finally
        {
            iterator.close();
            storeAreas.dispose();
        }
        int teller = 0;
        if (centroids != null)
        {
            for (NTMNode centroid : centroids.values())
            {
                boolean found = false;
                if (areas.containsKey(centroid.getId()))
                {
                    found = true;
                    teller++;
                }
                if (!found)
                {
                    areas.put(centroid.getId(), BuildGraph.createMissingArea(centroid));
                    System.out.println("Centroid not found: create area for " + centroid.getId());
                }
            }
        }
        System.out.println("found : " + teller);
        return areas;
    }
    /**
     * @param shapeFileName String; the nodes shapefile to read
     * @param numberType String;
     * @param returnCentroid boolean; , if true only loop through the centroid/zones (in case of mixed nodes and centroids)
     * @param allCentroids boolean; , if true: the file only contains centroids (a centroid file)
     * @return map of (shape file) nodes with nodenr as the key
     * @throws IOException on error
     */
    public static Map ReadNodes(final NTMModel model, final String shapeFileName, final String numberType,
            boolean returnCentroid, boolean allCentroids) throws IOException
    {
        /*-
         * the_geom class com.vividsolutions.jts.geom.Point POINT (190599 325650)
         * NODENR class java.lang.Long 18
         * NAME class java.lang.String 
         * X class java.lang.Double 190599.0
         * Y class java.lang.Double 325650.0
         * ...
         */
        URL url;
        if (new File(shapeFileName).canRead())
        {
            url = new File(shapeFileName).toURI().toURL();
        }
        else
        {
            url = ShapeFileReader.class.getResource(shapeFileName);
        }
        ShapefileDataStore storeNodes = (ShapefileDataStore) FileDataStoreFinder.getDataStore(url);
        Map nodes = new LinkedHashMap<>();
        SimpleFeatureSource featureSourceNodes = storeNodes.getFeatureSource();
        SimpleFeatureCollection featureCollectionNodes = featureSourceNodes.getFeatures();
        SimpleFeatureIterator iterator = featureCollectionNodes.features();
        try
        {
            while (iterator.hasNext())
            {
                SimpleFeature feature = iterator.next();
                Point p = (Point) feature.getAttribute("the_geom");
                Coordinate point = new Coordinate(p.getX(), p.getY());
                String nr = CsvFileReader.removeQuotes(String.valueOf(feature.getAttribute(numberType)));
                boolean addThisNode = false;
                TrafficBehaviourType type = null;
                if (returnCentroid)
                {
                    if (nr.substring(0, 1).equals("C") || allCentroids)
                    {
                        addThisNode = true;
                        type = TrafficBehaviourType.NTM;
                    }
                }
                else
                {
                    if (nr == null)
                    {
                        System.out.println("null found");
                    }
                    if (!nr.substring(0, 1).equals("C"))
                    {
                        addThisNode = true;
                        type = TrafficBehaviourType.ROAD;
                    }
                }
                if (addThisNode)
                {
                    double x;
                    double y;
                    if (feature.getAttribute("X") != null)
                    {
                        x = (double) feature.getAttribute("X");
                        y = (double) feature.getAttribute("Y");
                    }
                    else
                    {
                        x = point.x;
                        y = point.y;
                    }
                    // initially, set the behaviour default to TrafficBehaviourType.ROAD
                    NTMNode node = new NTMNode(model.getNetwork(), nr, point, type);
                    nodes.put(nr, node);
                }
            }
        }
        catch (Exception problem)
        {
            problem.printStackTrace();
        }
        finally
        {
            iterator.close();
            storeNodes.dispose();
        }
        System.out.println("aantal knopen (353): geteld " + nodes.size());
        return nodes;
    }
    /*    *//**
             * @param number
             * @return nr: the number of the Node without characters
             */
    /*
     * public static String NodeCentroidNumber(String number) { // String nr = null; number =
     * CsvFileReader.removeQuotes(number); String[] names = number.split(":"); String name = names[0]; if (name.charAt(0) ==
     * 'C') { name = name.substring(1); // nr = (long) Long.parseLong(name); } return name; }
     */
    /**
     * @param number String;
     * @return nr: the number of the Node without characters
     */
    public static boolean inspectNodeCentroid(String number)
    {
        boolean isCentroid = false;
        number = CsvFileReader.removeQuotes(number);
        String[] names = number.split(":");
        String name = names[0];
        if (name.charAt(0) == 'C')
        {
            isCentroid = true;
        }
        return isCentroid;
    }
    /**
     * @param shapeFileName String; the nodes shapefile to read
     * @param links Map<String,NTMLink>; : returns the file with real links
     * @param connectors Map<String,NTMLink>; returns the file with artificial links to a centroid/zone
     * @param nodes Map<String,NTMNode>; the map of nodes to retrieve start and end node
     * @param centroids Map<String,NTMNode>; the centroids to check start and end Node
     * @param lengthUnit String;
     * @param linkCapacityNumberOfHours Double;
     * @throws IOException on error
     */
    public static void readLinks(final NTMModel model, final String shapeFileName, Map links,
            Map connectors, Map nodes, Map centroids, String lengthUnit,
            Double linkCapacityNumberOfHours) throws IOException
    {
        /*-
         * the_geom class com.vividsolutions.jts.geom.MultiLineString MULTILINESTRING ((232250.38755446894 ...
         * LINKNR class java.lang.Long 1
         * NAME class java.lang.String 
         * DIRECTION class java.lang.Long 1
         * LENGTH class java.lang.Double 1.80327678
         * ANODE class java.lang.Long 684088
         * BNODE class java.lang.Long 1090577263
         * LINKTAG class java.lang.String 967536
         * WEGTYPEAB class java.lang.String mvt
         * TYPEWEGVAB class java.lang.String asw 2x2 (8600)
         * TYPEWEG_AB class java.lang.String 12 Autosnelweg 2x2
         * SPEEDAB class java.lang.Double 120.0
         * CAPACITYAB class java.lang.Double 8600.0
         * ...
         */
        URL url;
        if (new File(shapeFileName).canRead())
        {
            url = new File(shapeFileName).toURI().toURL();
        }
        else
        {
            url = ShapeFileReader.class.getResource(shapeFileName);
        }
        ShapefileDataStore storeLinks = (ShapefileDataStore) FileDataStoreFinder.getDataStore(url);
        SimpleFeatureSource featureSourceLinks = storeLinks.getFeatureSource();
        SimpleFeatureCollection featureCollectionLinks = featureSourceLinks.getFeatures();
        SimpleFeatureIterator iterator = featureCollectionLinks.features();
        try
        {
            while (iterator.hasNext())
            {
                SimpleFeature feature = iterator.next();
                GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
                Geometry geometry = (Geometry) feature.getAttribute("the_geom");
                Coordinate[] coords = geometry.getCoordinates();
                LineString line = geometryFactory.createLineString(coords);
                String nr = String.valueOf(feature.getAttribute("LINKNR"));
                String nrBA = nr + "_BA";
                String name = String.valueOf(feature.getAttribute("NAME"));
                // the reason to use String.valueOf(...) is that the .dbf files sometimes use double,
                // but also represent LENGTH by a string ....
                double lengthIn = Double.parseDouble(String.valueOf(feature.getAttribute("LENGTH")));
                Length length = null;
                if (lengthUnit.equals("kilometer"))
                {
                    length = new Length(lengthIn, LengthUnit.KILOMETER);
                }
                else if (lengthUnit.equals("meter"))
                {
                    length = new Length(lengthIn, LengthUnit.METER);
                }
                short direction = (short) Long.parseLong(String.valueOf(feature.getAttribute("DIRECTION")));
                String lNodeA = String.valueOf(feature.getAttribute("ANODE"));
                String lNodeB = String.valueOf(feature.getAttribute("BNODE"));
                // long lNodeB = NodeCentroidNumber(String.valueOf(feature.getAttribute("BNODE")));
                String linkTag = (String) feature.getAttribute("LINKTAG");
                String wegtype = (String) feature.getAttribute("WEGTYPEAB");
                String typeWegVak = (String) feature.getAttribute("TYPEWEGVAB");
                String typeWeg = (String) feature.getAttribute("TYPEWEG_AB");
                Double speedIn = Double.parseDouble(String.valueOf(feature.getAttribute("SPEEDAB")));
                Speed speed = new Speed(speedIn, SpeedUnit.KM_PER_HOUR);
                double capacityIn =
                        Double.parseDouble(String.valueOf(feature.getAttribute("CAPACITYAB"))) / linkCapacityNumberOfHours;
                Frequency capacity = new Frequency(capacityIn, FrequencyUnit.PER_HOUR);
                int hierarchy = 0;
                // new DoubleScalar.Abs(shpLink.getLength(), LengthUnit.KILOMETER);
                // create the link or connector to a centroid....
                NTMNode centroidA = centroids.get(lNodeA);
                NTMNode centroidB = centroids.get(lNodeB);
                NTMNode nodeA = nodes.get(lNodeA);
                NTMNode nodeB = nodes.get(lNodeB);
                boolean nodeACentroid = false;
                boolean nodeBCentroid = false;
                if (centroidA == null && centroidB == null) // all normal links....
                {
                    if (nodeA != null && nodeB != null)
                    {
                        NTMLink linkAB = null;
                        NTMLink linkBA = null;
                        LinkData linkData = new LinkData(name, linkTag, wegtype, typeWegVak, typeWeg);
                        linkAB = new NTMLink(model.getNetwork(), model.getSimulator(), new OTSLine3D(line), nr, length, nodeA,
                                nodeB, speed, null, capacity, TrafficBehaviourType.ROAD, linkData);
                        linkData = new LinkData(name + "_BA", linkTag, wegtype, typeWegVak, typeWeg);
                        linkBA = new NTMLink(model.getNetwork(), model.getSimulator(), new OTSLine3D(line), nrBA, length, nodeB,
                                nodeA, speed, null, capacity, TrafficBehaviourType.ROAD, linkData);
                        if (direction == 1)
                        {
                            links.put(nr, linkAB);
                        }
                        else if (direction == 2)
                        {
                            links.put(nrBA, linkBA);
                        }
                        else if (direction == 3)
                        {
                            links.put(nr, linkAB);
                            links.put(nrBA, linkBA);
                        }
                    }
                    else
                    {
                        System.out.println("Node lNodeA=" + lNodeA + " or lNodeB=" + lNodeB + " not found for linknr=" + nr
                                + ", name=" + name);
                    }
                }
                else
                { // possibly a link that connects to a centroid
                  // but first test the geometry of the node/centroid: is it a node or is it a centroid?
                    if (centroidA != null)
                    {
                        if (testGeometry(geometry.getCoordinates()[0], centroidA.getPoint().getCoordinate()))
                        {
                            nodeACentroid = true;
                        }
                    }
                    if (centroidB != null)
                    {
                        if (testGeometry(geometry.getCoordinates()[geometry.getCoordinates().length - 1],
                                centroidA.getPoint().getCoordinate()))
                        {
                            nodeBCentroid = true;
                        }
                    }
                    if (nodeACentroid && nodeBCentroid) // should not happen
                    {
                        System.out.println("Strange connector!!!: both Centroids lNodeA= " + centroidA + " or lNodeB= "
                                + centroidB + " connected to linknr=" + nr + ", name=" + name);
                    }
                    else if (nodeACentroid)
                    {
                        NTMLink linkAB = null;
                        NTMLink linkBA = null;
                        LinkData linkData = new LinkData(name, linkTag, wegtype, typeWegVak, typeWeg);
                        linkAB = new NTMLink(model.getNetwork(), model.getSimulator(), new OTSLine3D(line), nr, length,
                                centroidA, nodeB, speed, null, capacity, TrafficBehaviourType.NTM, linkData);
                        linkData = new LinkData(name + "_BA", linkTag, wegtype, typeWegVak, typeWeg);
                        linkBA = new NTMLink(model.getNetwork(), model.getSimulator(), new OTSLine3D(line), nrBA, length, nodeB,
                                centroidA, speed, null, capacity, TrafficBehaviourType.NTM, linkData);
                        if (direction == 1)
                        {
                            connectors.put(nr, linkAB);
                        }
                        else if (direction == 2)
                        {
                            connectors.put(nrBA, linkBA);
                        }
                        else if (direction == 3)
                        {
                            connectors.put(nr, linkAB);
                            connectors.put(nrBA, linkBA);
                        }
                    }
                    else if (nodeBCentroid)
                    {
                        NTMLink linkAB = null;
                        NTMLink linkBA = null;
                        LinkData linkData = new LinkData(name, linkTag, wegtype, typeWegVak, typeWeg);
                        linkAB = new NTMLink(model.getNetwork(), model.getSimulator(), new OTSLine3D(line), nr, length, nodeA,
                                centroidB, speed, null, capacity, TrafficBehaviourType.NTM, linkData);
                        linkData = new LinkData(name + "_BA", linkTag, wegtype, typeWegVak, typeWeg);
                        linkBA = new NTMLink(model.getNetwork(), model.getSimulator(), new OTSLine3D(line), nrBA, length,
                                centroidB, nodeA, speed, null, capacity, TrafficBehaviourType.NTM, linkData);
                        if (direction == 1)
                        {
                            connectors.put(nr, linkAB);
                        }
                        else if (direction == 2)
                        {
                            connectors.put(nrBA, linkBA);
                        }
                        else if (direction == 3)
                        {
                            connectors.put(nr, linkAB);
                            connectors.put(nrBA, linkBA);
                        }
                    }
                    else
                    // should not happen
                    {
                        LinkData linkData = new LinkData(name, linkTag, wegtype, typeWegVak, typeWeg);
                        NTMLink link = new NTMLink(model.getNetwork(), model.getSimulator(), new OTSLine3D(line), nr, length,
                                nodeA, nodeB, speed, null, capacity, TrafficBehaviourType.ROAD, linkData);
                        links.put(nr, link);
                    }
                }
            }
        }
        catch (Exception problem)
        {
            problem.printStackTrace();
        }
        finally
        {
            iterator.close();
            storeLinks.dispose();
        }
    }
    /**
     * @param coordinate Coordinate;
     * @param centroid Coordinate;
     * @return if TRUE: the points match geographically
     */
    public static boolean testGeometry(final Coordinate coordinate, final Coordinate centroid)
    {
        boolean geomEqual = false;
        if (Math.abs(coordinate.x - centroid.x) < 1)
        {
            if (Math.abs(coordinate.y - centroid.y) < 1)
            {
                geomEqual = true;
            }
        }
        return geomEqual;
    }
    /**
     * @param shapeFileName String; the areas shapefile to read
     * @throws IOException on error
     */
    public static void shapeFileInfo(final String shapeFileName) throws IOException
    {
        URL url;
        if (new File(shapeFileName).canRead())
        {
            url = new File(shapeFileName).toURI().toURL();
        }
        else
        {
            url = ShapeFileReader.class.getResource(shapeFileName);
        }
        ShapefileDataStore store = (ShapefileDataStore) FileDataStoreFinder.getDataStore(url);
        SimpleFeatureSource featureSource = store.getFeatureSource();
        SimpleFeatureCollection featureCollection = featureSource.getFeatures();
        SimpleFeatureIterator iterator = featureCollection.features();
        try
        {
            while (iterator.hasNext())
            {
                SimpleFeature feature = iterator.next();
                Collection areaProperties = feature.getProperties();
                for (Property p : areaProperties)
                {
                    System.out.println(p.getName() + " " + p.getValue().getClass() + " " + p.getValue().toString());
                }
                return;
            }
        }
        catch (Exception problem)
        {
            problem.printStackTrace();
        }
        finally
        {
            iterator.close();
            store.dispose();
        }
    }
}