package eu.dnetlib.espas.sosservice;

import eu.dnetlib.espas.exception.OwsException;
import eu.dnetlib.espas.exception.OwsExceptionCode;
import eu.dnetlib.espas.exception.Tuple;
import eu.dnetlib.miscutils.datetime.DateUtils;
import org.apache.log4j.Logger;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressWarnings({"UnusedParameters", "UnusedDeclaration"})
public class SOSServlet implements HttpRequestHandler {
    private final Logger logger = Logger.getLogger(SOSServlet.class);
    private SOSWService sos = null;

    public static void main(String[] args) {
        System.out.println(DateUtils.now_ISO8601());
        System.out.println(new DateUtils().parse("2010-07-08T18:45:31+0300"));
    }

    @Override
    public void handleRequest(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        try {

            Map<String, String> parameters = this.getParameters(req);
            /*
            http://localhost:8080/espas/sos?service=SOS&version=2.0.0&request=GetResult&temporalFilter=om:phenomenonTime,2014-02-01T00:00:00Z/2014-02-03T00:00:00Z&namespaces=xmlns(om%2Chttp%3A%2F%2Fwww.opengis.net%2Fom%2F2.0)&offering=http://resources.espas-fp7.eu/compositeProcess/noa/athDig-artist/1&observedProperty=http://ontology.espas-fp7.eu/observedProperty/MinimumFrequency_FirstHop_AllLayers
            */
            String service = req.getParameter("service");
            String version = req.getParameter("version");
            String request = req.getParameter("request");

            validateRequest(service, request, version);
            validateParameters(parameters);

            if (request != null) {
                resp.setContentType("application/xml");
                resp.setCharacterEncoding("UTF-8");

                if (request.equals("GetCapabilities")) {

                    throw new OwsException(request, OwsExceptionCode.OPERATION_NOT_SUPPORTED);

                } else if (request.equals("GetResultTemplate")) {

                    this.getResultTemplate(resp.getWriter(), parameters);

                } else if (request.equals("GetResult")) {

                    this.getResult(resp.getWriter(), parameters);

                } else {

                    throw new OwsException(request, OwsExceptionCode.OPERATION_NOT_SUPPORTED);

                }

            }
        } catch (OwsException e) {
            logger.error("OwsException", e);
            resp.setStatus(e.getStatus());
            resp.getWriter().append(e.getMessage());
        } catch (Exception e) {
            resp.setContentType("text/plain");
            resp.setStatus(500);
            logger.error("Exception at Servlet", e);
        }
    }

    private Map<String, String> getParameters(HttpServletRequest req) {
        Map<String, String> parameters = new HashMap<String, String>();

        for (Object key : req.getParameterMap().keySet())
            parameters.put(key.toString().toLowerCase(), req.getParameter(key.toString()));

        if (parameters.get("version") == null)
            parameters.put("version", "2.0.0");

        return parameters;
    }

    private void validateParameters(Map<String, String> parameters) throws OwsException {
        Map<String, Tuple<String, OwsExceptionCode>> exceptions = new HashMap<String, Tuple<String, OwsExceptionCode>>();

        for (String key : parameters.keySet()) {
            String value = parameters.get(key);

            if ((key.equals("temporalfilter") ||
                    key.equals("spatialfilter") ||
                    key.equals("featureofinterest") ||
                    key.equals("namespaces") ||
                    key.equals("xmlwrapper")) &&
                    value == null)
                continue;

            if (parameters.get(key) == null) {
                exceptions.put(key, new Tuple<String, OwsExceptionCode>("A needed parameter is missing", OwsExceptionCode.MISSING_PARAMETER));
            } else if (parameters.get(key).isEmpty()) {
                exceptions.put(key, new Tuple<String, OwsExceptionCode>("A needed parameter is empty", OwsExceptionCode.INVALID_PARAMETER));
            } else {
                if (key.equals("offering")) {
                    String offering = parameters.get("offering");

                    // Check that offering has a valid url form
                    if (!offering.matches("(http(?:s)?\\:\\/\\/[a-zA-Z0-9\\-]+(?:\\.[a-zA-Z0-9\\-]+)*\\.[a-zA-Z]{2,6}(?:\\/?|(?:\\/[\\w\\-]+)*)(?:\\/?|\\/\\w+\\.[a-zA-Z]{2,4}(?:\\?[\\w]+\\=[\\w\\-]+)?)?(?:\\&[\\w]+\\=[\\w\\-]+)*(#?([a-zA-Z0-9\\-\\_]+))?)")) {

                        exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'offering' is not a valid URL", OwsExceptionCode.INVALID_PARAMETER));

                    } else {
                        Pattern processPattern;
                        Pattern capabilityPattern;
                        Matcher processMatcher;
                        Matcher capabilityMatcher;
                        try {
                            String finalOffering = "";
                            processPattern = Pattern.compile("(compositeProcess|acquisition|computation|observation|observationCollection)/(.*?)/(.*?)/([0-9]+)");//\\#(.*?)$");
                            capabilityPattern = Pattern.compile(".*\\#(.*?)$");

                            processMatcher = processPattern.matcher(offering);
                            capabilityMatcher = capabilityPattern.matcher(offering);

                            if (processMatcher.find()) {
                                String process = processMatcher.group(1);
                                String namespace = processMatcher.group(2);
                                String localID = processMatcher.group(3);
                                String version = processMatcher.group(4);

//                                finalOffering = "http://resources.espas-fp7.eu/" + process + "/" + namespace + "/" + localID + "/" + version;
                                finalOffering = process + "/" + namespace + "/" + localID + "/" + version;
                                if (capabilityMatcher.find()) {
                                    finalOffering += "/" + capabilityMatcher.group(1);
                                }
                            } else {
                                logger.error("Offering " + offering + " not matching to a valid process (compositeProcess, acquisition, computation, observation, observationCollection)");
                                exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'offering' is not valid", OwsExceptionCode.INVALID_PARAMETER));
                            }
                            parameters.put(key, finalOffering);
                        } catch (Exception e) {
                            logger.error("Offering " + offering + " has issues...", e);
                            exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'offering' is not valid", OwsExceptionCode.INVALID_PARAMETER));
                        }
                    }
                } else if (key.equals("observedproperty")) {
                    String observedProperty = parameters.get("observedproperty");

                    // Check that observedProperty has a valid url form
                    if (!observedProperty.matches("(http(?:s)?\\:\\/\\/[a-zA-Z0-9\\-]+(?:\\.[a-zA-Z0-9\\-]+)*\\.[a-zA-Z]{2,6}(?:\\/?|(?:\\/[\\w\\-]+)*)(?:\\/?|\\/\\w+\\.[a-zA-Z]{2,4}(?:\\?[\\w]+\\=[\\w\\-]+)?)?(?:\\&[\\w]+\\=[\\w\\-]+)*)")) {
                        exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'observedProperty' is not a valid URL", OwsExceptionCode.INVALID_PARAMETER));
                    }
                    parameters.put(key, observedProperty);

                } else if (key.equals("namespaces")) {
                    // check namespace for having the right format. The format shall be xmlns(prefix,namespace_uri).
                    // Multiple namespaces shall be bound by specifying a comma separated list of xmlns() values.
                    // Notice: no spaces after commas ',' either between xmlns()s or within them
                    // namespaces=xmlns(ns1,http://www.opengis.net/samplingSpatial/2.0),xmlns(ns2,http://www.opengis.net/om/ 2.0)
                    if (!parameters.get("namespaces").matches("^xmlns\\([A-Za-z][A-Za-z0-9]*,(http(?:s)?\\:\\/\\/[a-zA-Z0-9\\-]+(?:\\.[a-zA-Z0-9\\-]+)*\\.[a-zA-Z]{2,6}(?:\\/?|(?:\\/[\\w\\-]+)*)(?:\\/?|\\/\\w+\\.[a-zA-Z]{2,4}(?:\\?[\\w]+\\=[\\w\\-]+)?)?(?:\\&[\\w]+\\=[\\w\\-]+)*).*\\)" +
                            "(,xmlns\\([A-Za-z][A-Za-z0-9]*,(http(?:s)?\\:\\/\\/[a-zA-Z0-9\\-]+(?:\\.[a-zA-Z0-9\\-]+)*\\.[a-zA-Z]{2,6}(?:\\/?|(?:\\/[\\w\\-]+)*)(?:\\/?|\\/\\w+\\.[a-zA-Z]{2,4}(?:\\?[\\w]+\\=[\\w\\-]+)?)?(?:\\&[\\w]+\\=[\\w\\-]+)*).*\\))*$")) {
                        exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'namespaces' is not valid", OwsExceptionCode.INVALID_PARAMETER));
                    }
                } else if (key.equals("featureofinterest")) {
                    // Check that featureOfInterest has a valid url form
                    String[] featureOfInterestUrls = parameters.get(key).split(",");
                    for (String featureOfInterest : featureOfInterestUrls) {
                        if (!featureOfInterest.matches("(http(?:s)?\\:\\/\\/[a-zA-Z0-9\\-]+(?:\\.[a-zA-Z0-9\\-]+)*\\.[a-zA-Z]{2,6}(?:\\/?|(?:\\/[\\w\\-]+)*)(?:\\/?|\\/\\w+\\.[a-zA-Z]{2,4}(?:\\?[\\w]+\\=[\\w\\-]+)?)?(?:\\&[\\w]+\\=[\\w\\-]+)*)")) {
                            exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'featureofinterest' is not valid", OwsExceptionCode.INVALID_PARAMETER));
                        }
                    }
                } else if (key.equals("xmlwrapper")) {
                    // Check that xmlWrapper if not null (default value is false) can take only
                    // two values true or false.
                    String xmlWrapper = parameters.get(key);
                    if (!xmlWrapper.matches("true|false|True|False|TRUE|FALSE"))
                        exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'xmlwrapper' is not valid", OwsExceptionCode.INVALID_PARAMETER));
                } else if (key.equals("temporalfilter")) {
                    // 'om:' with the name of the filter should be at the beginning of the filter.
                    // symbols ',' and '=' cannot be together
                    // In the first regex we check that if ',' exists then it requires the '/' symbol
                    // which is necessarily somewhere in the middle and not at the end...
                    //
                    // In the second regex, if the '=' symbol exists, then the ',' should not appear in any case.
                    String[] temporalFilters = parameters.get(key).split(",,");

                    for (String filter : temporalFilters) {
                        if (!filter.matches("^[A-Za-z][A-Za-z0-9]*:([A-Z]*[a-z]*)*,((?!=)(?!/).)*/((?!/)(?!=).)+$") &&
                                !filter.matches("^[A-Za-z][A-Za-z0-9]*:([A-Z]*[a-z]*)*,((?!,)(?!=)(?!/).)*$"))
                            exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter 'temporalfilter' has not valid format", OwsExceptionCode.INVALID_PARAMETER));
                        else {
                            if (parameters.get("namespaces") != null && !parameters.get("namespaces").matches(".*xmlns\\(" + filter.substring(0, filter.indexOf(":")) + ",.*\\).*"))
                                exceptions.put(key, new Tuple<String, OwsExceptionCode>("Parameter namespaces is not valid", OwsExceptionCode.INVALID_PARAMETER));
                            else if (parameters.get("namespaces") == null)
                                exceptions.put("namespaces", new Tuple<String, OwsExceptionCode>("Parameter 'namespaces' is missing", OwsExceptionCode.MISSING_PARAMETER));
                        }
                    }
                }
            }
        }

        if (!exceptions.isEmpty())
            throw new OwsException(exceptions);
        else {
            logger.info("Valid parameters for SOS request:");
            for (String s : parameters.keySet())
                logger.info("[" + s + "]: " + parameters.get(s));
        }
    }

    private void getResult(PrintWriter pw, Map<String, String> parameters) throws OwsException {
        sos.getResult(pw, parameters);
    }

    private void getResultTemplate(PrintWriter pw, Map<String, String> parameters) throws OwsException {
        sos.getResultTemplate(pw, parameters);
    }

    private void validateRequest(String service, String request, String typeName)
            throws Exception {
    }

    public SOSWService getSos() {
        return sos;
    }

    public void setSos(SOSWService sos) {
        this.sos = sos;
    }
}