package eu.dnetlib.espas.sosservice;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import eu.dnetlib.espas.exception.OwsException;
import eu.dnetlib.espas.exception.OwsExceptionCode;

public class SOSHelper {
    private final Logger logger = Logger.getLogger(SOSHelper.class);

    public String getObservedProperty(String observedProperty) {
        String urlLabel = null;
        Pattern pattern = Pattern.compile("http://ontology.espas-fp7.eu/observedProperty/(.*)");
        Matcher matcher = pattern.matcher(observedProperty);
        if (matcher.find()) {
            urlLabel = matcher.group(1);
        }

        return urlLabel;
    }

    public Set<Map<String, String>> temporalQueryParser(String temporalFilter) {
        //temporalFilter=om:phenomenonTime,2009-01-10T10:00:00Z/2009-01-10T11:00:00Z
        //temporalFilter=om:phenomenonTime,2009-01-10T10:00:00Z/2009-01-10T11:00:00Z,,om:resultTime,2009-01-10T10:00:00Z
        Set<Map<String, String>> parsedFilters = new HashSet<Map<String, String>>();
        String[] temporalFilters = temporalFilter.split(",,");
        for (String filter : temporalFilters) {
            Map<String, String> tempPeriod = new HashMap<String, String>();
            if (filter.matches("^[A-Za-z][A-Za-z0-9]*:([A-Z]*[a-z]*)*,((?!=)(?!/).)*/((?!/)(?!=).)+$")) {
                String[] filterData = filter.split(",");
                tempPeriod.put(filterData[0] + ".from", filterData[1].split("/")[0]);
                tempPeriod.put(filterData[0] + ".to", filterData[1].split("/")[1]);
            } else if (filter.matches("^[A-Za-z][A-Za-z0-9]*:([A-Z]*[a-z]*)*,((?!,)(?!=)(?!/).)*$")) {
                tempPeriod.put(filter.substring(0, filter.indexOf(",")) + ".instant", filter.substring(filter.indexOf(",") + 1, filter.length()));
            }
            parsedFilters.add(tempPeriod);
        }
        return parsedFilters;
    }

    public String readXml(String url) throws IOException {
        StringBuilder xmlBuilder = new StringBuilder();
        final String USER_AGENT = "Mozilla/5.0";

        HttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(url);
        BufferedReader rd;

        request.addHeader("User-Agent", USER_AGENT);
        org.apache.http.HttpResponse response = client.execute(request);
        rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

        String line;
        while ((line = rd.readLine()) != null) {
            xmlBuilder.append(line);
        }
        return xmlBuilder.toString();
    }

    public String searchResources(String processName, String processCapability, SOSConfiguration configuration) {
        List<String> fileNames = configuration.getFileNames();
        if (fileNames.size() > 0) {
            for (String fileName : fileNames) {
                if (fileName.contains(processName) && fileName.contains(processCapability)) {
                    return fileName;
                }
            }
        }
        return null;
    }

    public Capability getProcessCapability(String url, final String observedProperty, final String processCapability) throws IOException, SAXException, ParserConfigurationException, OwsException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser saxParser = factory.newSAXParser();
        final List<Capability> capabilities = new ArrayList<Capability>();

        DefaultHandler handler = new DefaultHandler() {
            boolean foundCapability = false;
            String value = "";
            Capability capability = null;

            public void startElement(String uri, String localName, String qName,
                                     Attributes attributes) throws SAXException {
                if (qName.contains("ESPAS_ProcessCapability")) {
                    capability = new Capability();
                } else if (qName.contains("observedProperty")) {
                    if (attributes.getValue("xlink:href").toLowerCase().contains(observedProperty.toLowerCase())) {
                        foundCapability = true;
                        capability.setObservedProperty(attributes.getValue("xlink:href"));
                    }
                } else if (qName.contains("dimensionalityInstance")) {
                    capability.setDimensionalityInstance(attributes.getValue("xlink:href"));
                } else if (qName.contains("dimensionalityTimeline")) {
                    capability.setDimensionalityTimeline(attributes.getValue("xlink:href"));
                } else if (qName.contains("units")) {
                    capability.setUnit(attributes.getValue("xlink:href"));
                }
            }

            public void endElement(String uri, String localName,
                                   String qName) throws SAXException {

                if (qName.contains("ESPAS_ProcessCapability")) {
                    if (foundCapability) {
                        if (processCapability.isEmpty()) {
                            capabilities.add(capability);
                        } else {
                            if (capability.getName().equals(processCapability))
                                capabilities.add(capability);
                        }
                    }
                    capability = null;
                    foundCapability = false;
                } else if (qName.toLowerCase().contains("name") && capability != null) {
                    capability.setName(value.trim());
                } else if (qName.toLowerCase().contains("validmin")  && capability != null) {
                    capability.setValidMin(value.trim());
                } else if (qName.toLowerCase().contains("validmax") && capability != null) {
                    capability.setValidMax(value.trim());
                } else if (qName.toLowerCase().contains("fillvalue") && capability != null) {
                    capability.setFillValue(value.trim());
                }
            }

            public void characters(char ch[], int start, int length) throws SAXException {
                value = new String(ch, start, length);
            }
        };

        saxParser.parse(url, handler);

        if (capabilities.size() > 1) {
            throw new OwsException("More than one ProcessCapabilities with the same ObservedProperty", OwsExceptionCode.MISSING_PARAMETER);
        } else if (capabilities.size() > 0)
            return capabilities.get(0);
        else return null;
    }

    public String CSWGetRecordByIdUrl(Properties properties,
                                   String prefix,
                                   String process,
                                   String namespace,
                                   String localID,
                                   String version) {
        String catalogueService = properties.getProperty(prefix + "csw.service");
        String id = "http://resources.espas-fp7.eu/" + process + "/" + namespace + "/" + localID + "/" + version;

        return catalogueService + "?service=CSW" +
                "&version=2.0.2" +
                "&outputFormat=application/xml" +
                "&request=GetRecordById&resulttype=results" +
                "&outputSchema=http://www.opengis.net/cat/csw/2.0.2" +
                "&Id=" + id;
    }

    /**
     * Makes a CSW GetRecordById call to a server and returns a String containing
     * the &lt;GetResultTemplateResponse&gt; of the SOS based on the given velocity template
     * @param velocityEngine the VelocityEngine necessary for the velocity template
     * @param properties properties to use in the CSW's GetRecordById call
     * @param prefix provides information for the configuration of the given implementation (CXS, HQI, JDBC)
     * @param sosConfiguration the SOS configuration
     * @param process resource's process
     * @param namespace resource's namespace
     * @param localID resource's localid
     * @param version resource's version
     * @param observedProperty getTemplate's observedProperty
     * @param capabilityName getTemplate's capability process name
     * @return String response of the &lt;GetResultTemplateResponse&gt; element
     * @throws Exception
     */
    public String getResourceTemplate(VelocityEngine velocityEngine, Properties properties, String prefix, SOSConfiguration sosConfiguration,
                                      String process, String namespace, String localID, String version,
                                      String observedProperty, String capabilityName) throws Exception {
        VelocityContext context = new VelocityContext();
        Template velocityTemplate = velocityEngine.getTemplate(properties.getProperty(prefix + "template"));
        String url;
        String resourceTemplate = null;
        String altName = getObservedProperty(observedProperty);
        if (sosConfiguration.getTemplateMode().equalsIgnoreCase("csw")) {
            url = CSWGetRecordByIdUrl(properties, prefix, process, namespace, localID, version);

            logger.info("Creating getRecordById request for the csw service:\n" + url);

        } else if (sosConfiguration.getTemplateMode().equalsIgnoreCase("filesystem")) {
            if (capabilityName.isEmpty())
                url = searchResources(localID, altName, sosConfiguration);
            else
                url = searchResources(localID, capabilityName, sosConfiguration);

        } else throw new Exception("No template mode has been set available. Check properties file for template.mode");

        if (url != null) {
            Capability capability = getProcessCapability(url, observedProperty, capabilityName);
            if (capability != null) {
                StringWriter writer = new StringWriter();

                context.put("capability", "capability");
                context.put("name", capability.getName());
                context.put("definition", capability.getObservedProperty());
                context.put("unit", capability.getUnit());
                context.put("dimensionalityInstance", capability.getDimensionalityInstance());
                context.put("dimensionalityTimeline", capability.getDimensionalityTimeline());
                context.put("validMin", capability.getValidMin());
                context.put("validMax", capability.getValidMax());
                context.put("fillValue", capability.getFillValue());
                context.put("tokenSeparator", properties.getProperty(prefix + "tokenseparator"));
                context.put("blockSeparator", properties.getProperty(prefix + "blockseparator"));

                velocityTemplate.merge(context, writer);
                resourceTemplate = writer.toString();

                writer.flush();
                writer.close();
            }
        }

        return resourceTemplate;
    }

    public String retrieveProperty(String observedProperty, String localID, String prefix, String key, Properties properties) throws OwsException {
        int selectedPart = 0;
        String value = null;
        StringBuilder propertyBuilder;
        String[] propertyParts = {observedProperty + ".", localID + ".", localID + "." + observedProperty + ".", ""};

        while (value == null) {
            try {
                propertyBuilder = new StringBuilder();
                propertyBuilder.append(prefix).append(propertyParts[selectedPart++]).append(key);
                value = properties.getProperty(propertyBuilder.toString());
            } catch (java.lang.ArrayIndexOutOfBoundsException e) {
                logger.error("Property not found for localId " + localID + " and observed property " + observedProperty + " for SQL clause " + key.toUpperCase());
                throw new OwsException("Property not found for localId " + localID + " and observed property " + observedProperty, OwsExceptionCode.INVALID_PROPERTY_OFFERING_COMBINATION);
            }
        }

        return value;
    }

    public void appendValue(String timeZone, String dateFormat, StringBuilder resultBuilder, String value) throws NullPointerException {
        Date date;
        DateFormat localFormat = new SimpleDateFormat(dateFormat);
        TimeZone localTimeZone = TimeZone.getTimeZone(timeZone);
        localFormat.setTimeZone(localTimeZone);

        try {
            date = localFormat.parse(value);
            DateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            resultBuilder.append(utcFormat.format(date));
        } catch (ParseException e) {
            try {
                double decimalValue = Double.parseDouble(value);
                resultBuilder.append(decimalValue);
            } catch (NumberFormatException e1) {
                resultBuilder.append(value);
            }
        }
    }

    public void appendValue(String timeZone, StringBuilder resultBuilder, String value) throws NullPointerException {
        String dateFormat = "yyyy-MM-dd HH:mm:ss";
        appendValue(timeZone, dateFormat, resultBuilder, value);
    }
}
