package eu.dnetlib.espas.sosservice.hqi;

import eu.dnetlib.espas.sosservice.*;
import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

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

@SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unused", "FieldCanBeLocal", "ThrowFromFinallyBlock"})
@Transactional
public class HqiSOSDataProvider implements SOSDataProvider {
    private final Logger logger = Logger.getLogger(HqiSOSDataProvider.class);

    private String timeZone = null;
    private SOSConfiguration hqiSOSConfiguration = null;
    private VelocityEngine velocityEngine = null;

    public void init() {
        velocityEngine = new VelocityEngine();

        velocityEngine.addProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.Log4JLogChute");
        velocityEngine.addProperty("runtime.log.logsystem.log4j.logger", logger.getName());

        velocityEngine.addProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
        velocityEngine.addProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        velocityEngine.addProperty(RuntimeConstants.UBERSPECT_CLASSNAME, org.apache.velocity.util.introspection.SecureUberspector.class.getName());

        velocityEngine.init();
    }

    @Override
    public String getResultTemplate(Map<String, String> parameters) throws OwsException {
        Properties properties = hqiSOSConfiguration.getProperties();
        SOSHelper helper = new SOSHelper();
        String resourceTemplate = null;
        boolean hasErrors = false;

        String observedProperty = parameters.get("observedproperty");
        String offering = parameters.get("offering");

        String[] processInfo = offering.split("\\/");
        try {
            if (processInfo.length > 0) {
                String process = processInfo[0];
                String namespace = processInfo[1];
                String localID = processInfo[2];
                String version = processInfo[3];
                String capabilityName = "";
                String hqiPrefix = "hqi.sos.";
                VelocityContext context = new VelocityContext();
                Template velocityTemplate = velocityEngine.getTemplate(properties.getProperty(hqiPrefix + "template"));
                String resourceFile = null;

                if (processInfo.length > 4)
                    capabilityName = processInfo[4];

                resourceTemplate = helper.getResourceTemplate(velocityEngine, properties, hqiPrefix, hqiSOSConfiguration,
                        process, namespace, localID, version, observedProperty, capabilityName);
            } else throw new Exception("No parameters available. Check servlet for the parsed parameters.");
        } catch (XPathExpressionException e) {
            logger.error("XPathExpressionException in getResult", e);
            hasErrors = true;
        } catch (ParserConfigurationException e) {
            logger.error("ParserConfigurationException in getResult", e);
            hasErrors = true;
        } catch (IOException e) {
            logger.error("IOException in getResult", e);
            hasErrors = true;
        } catch (SAXException e) {
            logger.error("SAXException in getResult", e);
            hasErrors = true;
        } catch (Exception e) {
            logger.error("Exception in getResult", e);
            hasErrors = true;
        } finally {
            if (hasErrors) {
                hasErrors = false;
                throw new OwsException("Check Log file for errors", OwsExceptionCode.NO_APPLICABLE_CODE);
            }
        }
        return resourceTemplate;
    }

    @Override
    public String getResult(Map<String, String> parameters) throws OwsException {
        Properties properties = hqiSOSConfiguration.getProperties();
        SOSHelper helper = new SOSHelper();

        String observedProperty = null;
        String offering = null;
        String resource = null;
        boolean hasErrors = false;
        String select = null;
        String from;
        String where;

        try {
            observedProperty = parameters.get("observedproperty");
            offering = parameters.get("offering");
            String[] processInfo = offering.split("\\/");

            if (processInfo.length > 0) {
                String process = processInfo[0];
                String namespace = processInfo[1];
                String localID = processInfo[2];
                String version = processInfo[3];
                String capabilityName = "";
                String hqiPrefix = "hqi.sos.";
                String service = properties.getProperty(hqiPrefix + "service");
                String blockSeparator = properties.getProperty(hqiPrefix + "blockseparator");
                String tokenSeparator = properties.getProperty(hqiPrefix + "tokenseparator");
                String url;
                String urlLabel;

                if (processInfo.length > 4)
                    capabilityName = processInfo[4];

                if (capabilityName.isEmpty())
                    urlLabel = helper.getObservedProperty(observedProperty);
                else
                    urlLabel = capabilityName;

                select = helper.retrieveProperty(urlLabel, localID, hqiPrefix, "select", properties);
                from = helper.retrieveProperty(urlLabel, localID, hqiPrefix, "from", properties);
                where = helper.retrieveProperty(urlLabel, localID, hqiPrefix, "where", properties);

                if (parameters.get("temporalfilter") != null && where != null) {
                    Set<Map<String, String>> temporalQueryParsedSet = helper.temporalQueryParser(parameters.get("temporalfilter"));

                    String startRegex = "(\\$\\{startdate\\})";
                    String endRegex = "(\\$\\{enddate\\})";
                    DateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
                    utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));

                    DateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd'%20'HH:mm:ss");
                    TimeZone localTimeZone = TimeZone.getTimeZone(timeZone);
                    localFormat.setTimeZone(localTimeZone);

                    for (int i = 0; i < temporalQueryParsedSet.size(); i++) {
                        Map<String, String> temporalQueryParsed = temporalQueryParsedSet.iterator().next();

                        for (String key : temporalQueryParsed.keySet()) {
                            Date date = utcFormat.parse(temporalQueryParsed.get(key));
                            if (key.contains("from")) {
                                where = where.replaceFirst(startRegex, localFormat.format(date));
                            }
                            if (key.contains("to")) {
                                where = where.replaceFirst(endRegex, localFormat.format(date));
                            }
                            if (key.contains("instant")) {
                                where = where.replaceFirst(startRegex, localFormat.format(date));
                                where = where.replaceFirst(endRegex, localFormat.format(date));
                            }
                        }
                    }
                    url = service + "?SELECT=" + select + "&FROM=" + from + "&SQLWHERE=" + where;
                } else if (where == null) {
                    url = service + "?SELECT=" + select + "&FROM=" + from;
                } else {
                    url = "";
                }

                resource = createXml(url, tokenSeparator, blockSeparator);
            } else {
                throw new OwsException("No parameters available. Check servlet for the parsed parameters.", OwsExceptionCode.MISSING_PARAMETER);
            }
        } catch (XPathExpressionException e) {
            logger.error("XPathExpressionException in getResult", e);
            hasErrors = true;
        } catch (ParserConfigurationException e) {
            logger.error("ParserConfigurationException in getResult", e);
            hasErrors = true;
        } catch (IOException e) {
            logger.error("IOException in getResult", e);
            hasErrors = true;
        } catch (SAXException e) {
            logger.error("SAXException in getResult", e);
            hasErrors = true;
        } catch (ParseException e) {
            logger.error("Timezone parse error in getResult", e);
            hasErrors = true;
        } finally {
            if (hasErrors) {
                hasErrors = false;
                if (select == null)
                    throw new OwsException("No entry for requested Offering", OwsExceptionCode.INVALID_PROPERTY_OFFERING_COMBINATION);

                throw new OwsException("Check Log file for errors", OwsExceptionCode.NO_APPLICABLE_CODE);
            }
        }

        return resource;
    }

    private String createXml(String url, String tokenSeparator, String blockSeparator) throws XPathExpressionException, ParserConfigurationException, IOException, SAXException {
        StringBuilder xmlBody = new StringBuilder();
        XPathFactory factory = XPathFactory.newInstance();
        XPath xPath = factory.newXPath();
        xPath.setNamespaceContext(new SOSNamespace());
        SOSHelper helper = new SOSHelper();

        XPathExpression resultValuesExpression = null;
        XPathExpression blockExpression = null;

        String xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<sos:GetResultResponse " +
                "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
                "xmlns:swe=\"http://www.opengis.net/swe/2.0\" " +
                "xmlns:swes=\"http://www.opengis.net/swes/2.0\" " +
                "xmlns:ows=\"http://www.opengis.net/ows/1.1\" " +
                "xmlns:sos=\"http://www.opengis.net/sos/2.0\" " +
                "xmlns:fes=\"http://www.opengis.net/fes/2.0\" " +
                "xmlns:gml=\"http://www.opengis.net/gml/3.2\" " +
                "xmlns:ogc=\"http://www.opengis.net/ogc\" " +
                "xmlns:om=\"http://www.opengis.net/om/2.0\" " +
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" " +
                "xsi:schemaLocation=\"http://www.opengis.net/sos/2.0 " +
                "http://schemas.opengis.net/sos/2.0/sos.xsd\">\n" +
                "<sos:resultValues>";
        String xmlFooter = "</sos:resultValues>\n" +
                "</sos:GetResultResponse>";

        //XPath configuration
        resultValuesExpression = xPath.compile("/vt:VOTABLE/vt:RESOURCE/vt:TABLE/vt:DATA/vt:TABLEDATA/vt:TR");
        blockExpression = xPath.compile("vt:TD");

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();

        InputSource inputSource = new InputSource(new ByteArrayInputStream(helper.readXml(url).getBytes("utf-8")));
        Document doc = db.parse(inputSource);

        NodeList TRList = (NodeList) resultValuesExpression.evaluate(doc, XPathConstants.NODESET);
        int resultCounter = 0;

        if (TRList != null && TRList.getLength() > 0) {

            for (int i = 0; i < TRList.getLength(); i++) {
                NodeList TDList = (NodeList) blockExpression.evaluate(TRList.item(i), XPathConstants.NODESET);
                if (TDList != null && TDList.getLength() > 0) {
                    StringBuilder resultBuilder = new StringBuilder();
                    String value = null;
                    for (int j = 0; j < TDList.getLength(); j++) {
                        Date date = null;
                        value = TDList.item(j).getTextContent();
                        try {
                            helper.appendValue(timeZone, "yyyy-MM-dd'T'HH:mm:ss", resultBuilder, value);
                        } catch (NullPointerException e) {
                            break;
                        }
                        resultBuilder.append(tokenSeparator);
                    }
                    if (value != null) {
                        resultCounter++;
                        xmlBody.append(resultBuilder.toString().replaceAll(",$", "")).append(blockSeparator);
                    }
                }
            }
        }
        xmlBody.insert(0, resultCounter + blockSeparator);
        xmlBody.insert(0, xmlHeader);
        xmlBody.append(xmlFooter);

        return xmlBody.toString();
    }

    public String getTimeZone() {
        return timeZone;
    }

    public void setTimeZone(String timeZone) {
        this.timeZone = timeZone;
    }

    public SOSConfiguration getHqiSOSConfiguration() {
        return hqiSOSConfiguration;
    }

    public void setHqiSOSConfiguration(SOSConfiguration hqiSOSConfiguration) {
        this.hqiSOSConfiguration = hqiSOSConfiguration;
    }
}
