package usda.weru.gis;

import java.awt.Color;
import javax.swing.JOptionPane;
import java.io.FileFilter;
import org.geotools.data.FeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.SLD;
import org.geotools.xml.styling.SLDParser;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.FilterFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import de.schlichtherle.truezip.file.TFile;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.map.FeatureLayer;
import org.geotools.styling.PolygonSymbolizer;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import usda.weru.util.ConfigData;
import usda.weru.util.Util;

public class MapData {

    private static final Logger LOGGER = Logger.getLogger(MapData.class);
    StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null);
    FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null);

    public static void prepareGISData(GISData data) throws Exception {

        TFile gisDir = new TFile(ConfigData.getDefault().getDataParsed(ConfigData.GISData));

        if (!gisDir.exists()) {
            LOGGER.warn("GIS data directory does not exist: " + gisDir.getAbsolutePath());
            return;
        }

        TFile[] shapefiles = Util.findFiles(gisDir, new FileFilter() {

            @Override
            public boolean accept(java.io.File f) {
                return f.getName().toLowerCase().endsWith(".shp");
            }
        }, true, -1);

        for (TFile shapefile : shapefiles) {

            // pos is the z position that the layer is set at
            int pos = 1000;

            String name = shapefile.getName().toLowerCase();

            if (name.startsWith("world_borders")) {
                pos = 10;
            } else if (name.startsWith("st99_")) {
                pos = 20;
            } else if (name.startsWith("co99_")) {
                pos = 30;
            } else if (name.startsWith("cmz")) {
                pos = 40;
            } else if (name.contains("cligen_boundary")) {
                pos = 51;
            } else if (name.startsWith("cligen_")) {
                pos = 50;
            } else if (name.startsWith("windgen_")) {
                pos = 60;
            }
            
            if (name.contains("_nofill")) {
                pos += 1;
            }

            createDatastore(data, name, shapefile.getAbsolutePath(), pos);
        }

    }

    private static void createDatastore(GISData data, String name, String path, int pos) {
        try {
            if (path == null) {
                return;
            }
            TFile file = new TFile(path);
            if (!file.exists()) {
                return;
            }
            URL url = file.toURI().toURL();
            Map<String, URL> params = new HashMap<>();
            params.put("url", url);
            //TODO: activate indexing
            DataStore store = DataStoreFinder.getDataStore(params);

            data.register(name, store, pos);

        } catch (IOException e) {
            LOGGER.error("Unable to read shapefile: " + path, e);
        }
    }

    public FeatureLayer createLayer(FeatureSource<SimpleFeatureType, SimpleFeature> source) {
        FeatureType schema = null;
        try {
            schema = source.getSchema();
        } catch(NoClassDefFoundError e) {
            //System.err.println("No class definition in MapData.createLayer");
        }

        try {

            //URI sourceURI = source.getDataStore().getInfo().getSource();
            
            // this is a terrible hack to get the filename of the given FeatureSource
            // but the filename seems to ONLY be accessible via the toString method
            String s = source.getDataStore().toString();
            Pattern p = Pattern.compile(".*file=([^,]*),.*");
            Matcher m = p.matcher(s);
            if (m.matches()) {
                s = m.group(1);
            } else {
                s = "";
            }
            URI sourceURI = new URI(s);
            // end terrible hack

            TFile file = new TFile(sourceURI);
            Style style = createStyle(file, schema);
            FeatureLayer layer = new FeatureLayer(source, style);
//            layer.setTitle(updateTitleName(file.getName()));
//            layer.setVisible(isLayerVisibleDefault(layer));
            return layer;
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return null;
        }

    }
    
//    protected String updateTitleName (String name) {
//        
//        String fillStr = name.contains("_nofill") ? " "+ConfigData.layersOutlineName : "";
//        
//        if (name.startsWith("world_borders")) {
//            name = ConfigData.worldBordersLayerName;
//        } else if (name.startsWith("st99_")) {
//            name = ConfigData.usStatesLayerName;
//        } else if (name.startsWith("co99_")) {
//            name = ConfigData.usCountiesLayerName;
//        } else if (name.startsWith("cmz")) {
//            name = "NRCS CMZ regions";
//        } else if (name.startsWith("WINDGEN_2018")) {
//            name = "Windgen interp. & polygon regions";
//        } else if (name.startsWith("CLIGEN_2013")) {
//            name = "Cligen polygon regions";
//        } else if (name.startsWith("cligen_boundary")) {
//            name = "Cligen boundary";
//        } else if (name.startsWith("CHN_adm1")) {
//            name = ConfigData.chinaProvLayerName;
//        } else if (name.startsWith("reflected")) {
//            name = "Reflected Windgen stations";
//        } else if (name.startsWith("int_boundary")) {
//            name = "Windgen interp. boundary";
//        } else if (name.startsWith("triangulation")) {
//            name = "Windgen triangulation boundaries";
//        } else if (name.endsWith(".shp")) {
//            name = name.substring(0, name.length() - 4);
//        }
//        return name + fillStr;
//    }
    
//    public static boolean isLayerVisibleDefault(FeatureLayer layer) {
//        String name = layer.getTitle();
//        if (name.startsWith(ConfigData.usCountiesLayerName)) {
//            return true;
//        } else if (name.startsWith(ConfigData.usStatesLayerName)) {
//            return true;
//        } else if (name.startsWith(ConfigData.worldBordersLayerName)) {
//            return true;
//        }
//        return false;
//    }
    
    private Style createStyle(TFile file, FeatureType schema) {
        TFile sld = toSLDFile(file);
        if (sld.exists()) {
            return createFromSLD(sld);
        }
        Class<?> type = schema.getGeometryDescriptor().getType().getBinding();
        if (type.isAssignableFrom(Polygon.class) || type.isAssignableFrom(MultiPolygon.class)) {
            return createPolygonStyle();
        } else if (type.isAssignableFrom(LineString.class) || type.isAssignableFrom(MultiLineString.class)) {
            return createLineStyle();
        } else {
            return createPointStyle();
        }
    }

    
    private Style createFromSLD(TFile sldFile) {
        SLDParser stylereader;
        try {
            stylereader = new SLDParser(styleFactory, sldFile);
            Style[] style = stylereader.readXML();
            return style[0];
        } catch (FileNotFoundException e) {
            JOptionPane.showMessageDialog(null, e.getMessage());
            e.printStackTrace();
            System.exit(0);

        }
        return null;
    }

    private Style createPointStyle() {
        Style style;
        PointSymbolizer symbolizer = styleFactory.createPointSymbolizer();
        symbolizer.getGraphic().setSize(filterFactory.literal(1));
        Rule rule = styleFactory.createRule();
        rule.symbolizers().add(symbolizer);
        FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();
        fts.rules().clear();
        fts.rules().add(rule);
        style = styleFactory.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }

    private Style createLineStyle() {
        Style style;

        LineSymbolizer symbolizer = styleFactory.createLineSymbolizer();
        SLD.setLineColour(symbolizer, Color.BLUE);
        symbolizer.getStroke().setWidth(filterFactory.literal(1));
        symbolizer.getStroke().setColor(filterFactory.literal(Color.BLUE));

        Rule rule = styleFactory.createRule();
        rule.symbolizers().add(symbolizer);
        FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();
        fts.rules().clear();
        fts.rules().add(rule);
        style = styleFactory.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }

    private Style createPolygonStyle() {
        Style style;
        PolygonSymbolizer symbolizer = styleFactory.createPolygonSymbolizer();
        Fill fill = styleFactory.createFill(filterFactory.literal("#FFAA00"), filterFactory.literal(0.5));

        symbolizer.setFill(fill);
        Rule rule = styleFactory.createRule();
        rule.symbolizers().add(symbolizer);
        FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();
        fts.rules().clear();
        fts.rules().add(rule);
        style = styleFactory.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }

    /**
     * Figure out the URL for the "sld" file
     * @param file
     * @return 
     */
    public TFile toSLDFile(TFile file) {
        String filename = file.getAbsolutePath();
        if (filename.endsWith(".shp") || filename.endsWith(".dbf") || filename.endsWith(".shx")) {
            filename = filename.substring(0, filename.length() - 4);
            filename += ".sld";
        } else if (filename.endsWith(".SLD")) {
            filename = filename.substring(0, filename.length() - 4);
            filename += ".SLD";
        }
        return new TFile(filename);
    }
}
