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.styling.SLDParser;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.FilterFactory;

import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.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 org.opengis.feature.type.Name;
import usda.weru.util.ConfigData;
import usda.weru.util.Util;

/**
 *
 * @author maxerdwien
 */
public class MapData {

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

    /**
     * Prompt the user for a file and open up ImageLab.
     *
     * @param data
     * @throws Exception 
     */
    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.contains("world")) {
                pos = 0;
                name = "world";
            } else if (name.startsWith("st")) {
                pos = 10;
                name = "us_states";
            } else if (name.startsWith("co")) {
                pos = 20;
                name = "us_counties";
            } else if (name.startsWith("cmz")) {
                pos = 30;
                name = "cmz";
            } else if (name.contains("cligen_boundary")) {
                pos = 51;
                //name = "cligen boundary";
            } else if (name.startsWith("cligen_")) {
                pos = 50;
                //name = "cligen area";
            } else if (name.startsWith("windgen_")) {
                pos = 60;
                //name = "windgen area";
            }

            if (name.endsWith(".shp")) {
                name = name.substring(0, name.length() - 3);
            }

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

    }

    /**
     *
     * @param name
     * @return
     */
    public static boolean isLayerVisibleDefault(Name name) {
        switch (name.toString()) {
            case "world":
                return true;
            case "us_states":
                return true;
            case "us_counties":
                return true;
            default:
                return false;
        }
    }

    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);
        }
    }

    /**
     *
     * @param source
     * @return
     */
    public FeatureLayer createLayer(FeatureSource<SimpleFeatureType, SimpleFeature> source) {
        FeatureType schema = source.getSchema();

        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(file.getName());
            return layer;
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return null;
        }

    }

    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);
    }
}
