package usda.weru.gis;

import org.locationtech.jts.geom.Geometry;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultRepository;
import org.geotools.feature.NameImpl;
import org.opengis.feature.Feature;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.type.Name;

/**
 *
 * @author Joseph A. Levin <joelevin@weru.ksu.edu>
 */
public class GISData extends DefaultRepository {

    private static final Logger LOGGER = Logger.getLogger(GISData.class.getName());
    private static GISData c_instance;
    private List<GISLookup<?>> c_lookups;
    private Map<Name, Integer> c_sortLayers;

    private GISData() {
        //TODO: actually use a config file to load gis data
        
        try {
            MapData.prepareGISData(this);
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Unable to initilize GISData", e);
        }
    }

    public static synchronized GISData getInstance() {
        if (c_instance == null) {
            c_instance = new GISData();
        }

        return c_instance;
    }

    // if 'lookup.supports(c)' is true, then it is guaranteed that 'lookup' is of
    // type 'T'.
    @SuppressWarnings({"unchecked", "rawtypes"})
    public <T> GISLookup<T> getLookup(Class<T> c) {

        synchronized (c) {
            List<GISLookup<T>> temp = new LinkedList<>();

            for (GISLookup lookup : lookups()) {
                if (lookup.supports(c)) {
                    temp.add(lookup);
                }
            }

            if (temp.size() == 1) {
                return temp.get(0);
            } else if (temp.size() > 1) {
                return new MergedGISLookup<T>(c, temp);
            } else {
                return null;
            }
        }
    }

    // ServiceLoader won't let us specify the type of a GISLookup
    @SuppressWarnings("rawtypes")
    private synchronized void initLookups() {
        c_lookups = new LinkedList<GISLookup<?>>();
        ServiceLoader<GISLookup> loader = ServiceLoader.load(GISLookup.class);
        Iterator<GISLookup> i = loader.iterator();
        while (i.hasNext()) {
            c_lookups.add(i.next());
        }
    }

    private synchronized List<GISLookup<?>> lookups() {
        if (c_lookups == null) {
            initLookups();
        }
        return c_lookups;
    }

    @Override
    public Set<Name> getNames() {
        Set<Name> names = super.getNames();
        //System.out.println("layer names in gisdata:"+names);
        List<Name> temp = new ArrayList<Name>();

        temp.addAll(0, names);
        Collections.sort(temp, new LayerSorter());
        return new LinkedHashSet<Name>(temp);
    }

    // 'getInstance().getLookup(o.getClass())' will always return a GISLookup<O>
    @SuppressWarnings("unchecked")
    public static <O> Geometry getGeometry(O o) throws IllegalArgumentException {
        if (o == null) {
            return null;
        }

        GISLookup<O> lookup = (GISLookup<O>) getInstance().getLookup(o.getClass());

        if (lookup == null) {
            throw new IllegalArgumentException(String.format(
                    "No GISLookup implementations found for class: %s", o.getClass()));
        }

        List<Feature> features = lookup.lookup(o);
        if (features == null || features.isEmpty()) {
            return null;
        }

        Feature firstFeature = features.get(0);

        GeometryAttribute attribute = firstFeature.getDefaultGeometryProperty();
        if (attribute == null) {
            return null;
        }

        Object value = attribute.getValue();
        if (value == null) {
            return null;
        }

        if (value instanceof Geometry) {
            return (Geometry) value;
        }

        return null;
    }

    public void register(String id, DataStore dataStore) throws IOException {
        register(id, dataStore, -1);
    }

    public void register(String id, DataStore dataStore, int sortLayer) throws IOException {
        if (c_sortLayers == null) {
            c_sortLayers = new HashMap<Name, Integer>();
        }
        if (dataStore != null) {
            c_sortLayers.put(new NameImpl(id), sortLayer);
        } else {
            c_sortLayers.remove(new NameImpl(id));
        }

        super.register(id, dataStore);
        for (GISLookup<?> lookup : lookups()) {
            lookup.register(id, dataStore);
        }
    }

    private class LayerSorter implements Comparator<Name> {

        @Override
        public int compare(Name o1, Name o2) {
            int i1 = c_sortLayers.get(o1);
            int i2 = c_sortLayers.get(o2);

            return i1 - i2;
        }
    }
}
