/*
 * COPYRIGHT (c) 2010 by Institute of Computer Science, 
 * Foundation for Research and Technology - Hellas
 * Contact: 
 *      POBox 1385, Heraklio Crete, GR-700 13 GREECE
 *      Tel:+30-2810-391632
 *      Fax: +30-2810-391638
 *      E-mail: isl@ics.forth.gr
 *      http://www.ics.forth.gr/isl/cci.html
 * 
 *  This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 
 * Unported License (http://creativecommons.org/licenses/by-sa/3.0/)
*/

package Transformation;

/**
 * @author Koutraki Maria (kutraki@ics.forth.gr)
 */


import URIidevelopment.URIPolicies;
import java.io.File;
import java.io.IOException;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

/*swkmmodel*/
import gr.forth.ics.swkm.model2.Model;
import gr.forth.ics.swkm.model2.ModelBuilder;
import gr.forth.ics.swkm.model2.io.Format;
import gr.forth.ics.swkm.model2.io.RdfIO;
import gr.forth.ics.swkm.vocabulary.Rdf;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.lang.reflect.Method;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

public class parserOfLidoFile {

    private Document doc = null;
    private ArrayList<LidoElements> dom;
    private String StarEntity = null;
    private String StarEntityUri = null;

    public parserOfLidoFile(parserOfMapping dpm, String file) {
        dom = new ArrayList<LidoElements>();
        try {
            doc = parserXML(new File(file));
        } catch (Exception error) {
            error.printStackTrace();
        }
    }

    public void transform(parserOfMapping dpm, String InputLidofile, String destinationPath) throws IOException {
        int exist = 0;

        Model model = ModelBuilder.newSparse().build();

        NodeList list = doc.getElementsByTagName("*");
        for (int i = 0; i < list.getLength(); i++) {
            // Get element
            Element element = (Element) list.item(i);
            for (int j = 0; j < dpm.elementsOfMapping.size(); j++) {
                exist = 0;
                if (element.getNodeName().equals(dpm.elementsOfMapping.get(j))) {
                    for (int k = 0; k < dom.size(); k++) {
                        if (dom.get(k).getNode().equals(element.getNodeName()) && element.getNodeName().equals("lido:eventType/lido:term") && element.getChildNodes().item(0).getNodeValue().equals(dom.get(k).getElement())) {
                            exist = 1;
                            break;
                        }
                    }
                    if (exist == 1) {
                        continue;
                    } else {
                        if (!element.hasChildNodes()) {
                            LidoElements mse = new LidoElements(element.getNodeName(), element.getAttribute("lido:value"));
                            createTriples(dpm, element.getNodeName(), element.getAttribute("lido:value"), element, model, InputLidofile);
                            dom.add(mse);
                            break;
                        } else {
                            LidoElements mse = new LidoElements(element.getNodeName(), element.getChildNodes().item(0).getNodeValue());
                            createTriples(dpm, element.getNodeName(), element.getChildNodes().item(0).getNodeValue(), element, model, InputLidofile);
                            dom.add(mse);
                            break;
                        }
                    }
                } else if ((element.getParentNode().getNodeName() + '/' + element.getNodeName()).equals(dpm.elementsOfMapping.get(j))) {
                    for (int k = 0; k < dom.size(); k++) {
                        if (dom.get(k).getNode().equals(element.getParentNode().getNodeName() + '/' + element.getNodeName()) && dom.get(k).getNode().equals("lido:eventType/lido:term") &&  element.getChildNodes().item(0).getNodeValue().equals(dom.get(k).getElement())) {
                            exist = 1;
                            break;
                        }
                    }
                    if (exist == 1) {
                        continue;
                    } else {
                        LidoElements mse = new LidoElements((element.getParentNode().getNodeName() + '/' + element.getNodeName()), element.getChildNodes().item(0).getNodeValue());
                        createTriples(dpm, (element.getParentNode().getNodeName() + '/' + element.getNodeName()), element.getChildNodes().item(0).getNodeValue(), element, model, InputLidofile);
                        dom.add(mse);
                        break;
                    }
                } else if (element.getParentNode().getParentNode() != null) {
                    if ((element.getParentNode().getParentNode().getNodeName() + '/' + element.getParentNode().getNodeName() + '/' + element.getNodeName()).equals(dpm.elementsOfMapping.get(j))) {
                        for (int k = 0; k < dom.size(); k++) {
                       
                            if (dom.get(k).getNode().equals((element.getParentNode().getParentNode().getNodeName() + '/' + element.getParentNode().getNodeName() + '/' + element.getNodeName())) && dom.get(k).getNode().equals("lido:eventType/lido:term") && element.getChildNodes().item(0).getNodeValue().equals(dom.get(k).getElement())) {
                                exist = 1;
                                break;
                            }
                        }
                        if (exist == 1) {
                            continue;
                        } else {
                            LidoElements mse = new LidoElements((element.getParentNode().getParentNode().getNodeName() + '/' + element.getParentNode().getNodeName() + '/' + element.getNodeName()), element.getChildNodes().item(0).getNodeValue());
                            createTriples(dpm, (element.getParentNode().getParentNode().getNodeName() + '/' + element.getParentNode().getNodeName() + '/' + element.getNodeName()), element.getChildNodes().item(0).getNodeValue(), element, model, InputLidofile);
                            dom.add(mse);
                            break;
                        }
                    }
                }
            }

        }
        String s = RdfIO.write(model, Format.RDFXML).withBase("http://baseUri").toString();
        System.err.println(s);
        fileCreation(destinationPath, "\\Results.rdf", s);
    }

    public Document parserXML(
            File file) throws SAXException, IOException, ParserConfigurationException {
        return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
    }

    public void printList() {
        for (int i = 0; i <
                dom.size(); i++) {
            System.out.println(dom.get(i).getNode() + "  " + dom.get(i).getElement());
        }

    }

    public void createTriples(parserOfMapping dpm, String nodeName, String nodeValue, Element element, Model model, String file) {


        ArrayList<ElementInfos> elementInfs = getElementInfosforNode(dpm, nodeName);
        ArrayList<CIDOCTriples> triples = new ArrayList<CIDOCTriples>();
        if (elementInfs.size() == 1) {
            Condition cond = elementInfs.get(0).getConditions();
            if (cond.getPath().equals("null") && cond.getValue().equals("null")) { //if "conditions" not exist
                triples = elementInfs.get(0).getTriples();
            } else { //if "conditions" exist
                try {
                    String path = cond.getPath();
                    Node conditionElementNode = getArgumentsRelativeXpath(element, path);
                    String conditionElement;
                    if (conditionElementNode != null) {
                        conditionElement = conditionElementNode.getNodeValue();
                    } else {
                        conditionElement = null;
                    }

                    if (conditionElement != null && conditionElement.equals(cond.getValue())) {
                        triples = elementInfs.get(0).getTriples();
                    }
                } catch (ParserConfigurationException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                } catch (SAXException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IOException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                } catch (XPathExpressionException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            produceTriple(triples, element, nodeName, nodeValue, model, file);
        } else if (elementInfs.size() > 1) { /* case there are more than one choices for the element. eg. EventType*/
		
            ArrayList<CIDOCTriples> triples2 = new ArrayList<CIDOCTriples>();
            int exist = 0;
            for (int c = 0; c < elementInfs.size(); c++) {

                Condition cond = elementInfs.get(c).getConditions();

                if (!cond.getPath().equals("null") && !cond.getValue().equals("null")) {//if "conditions" exist
                    try {
                        String path = cond.getPath();
                        Node conditionElementNode = getArgumentsRelativeXpath(element, path);
                        String conditionElement;
                        if (conditionElementNode != null) {
                            conditionElement = conditionElementNode.getNodeValue();
                        } else {
                            conditionElement = null;
                        }
                        if (conditionElement != null && conditionElement.equals(cond.getValue())) {
                            exist = 1;
                            triples = elementInfs.get(c).getTriples();
                            break;
                        }
                    } catch (IOException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (SAXException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (ParserConfigurationException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (XPathExpressionException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } else { //if "conditions" don't exist
                    exist =0;
                    triples2 = elementInfs.get(c).getTriples();
                }

            }
            if (exist == 1) {
                produceTriple(triples, element, nodeName, nodeValue, model, file);
            } else {
                produceTriple(triples2, element, nodeName, nodeValue, model, file);
            }
        }
    }

    private void produceTriple(ArrayList<CIDOCTriples> triples, Element element, String nodeName, String nodeValue, Model model, String file) {
        for (int i = 0; i < triples.size(); i++) {

            String SubjectEntity = triples.get(i).getSubject().getEntity();
            String SubjectFunctionName = triples.get(i).getSubject().getUriFunctionName();
            ArrayList<String> SubjectFunctionArguments = triples.get(i).getSubject().getUriFunctionArguments();

            String uriForSubject = createTripleforSubject(SubjectFunctionName, SubjectFunctionArguments, SubjectEntity, StarEntity, StarEntityUri, element, model, file);

            String ObjectEntity = triples.get(i).getObject().getEntity();
            String ObjectFunctionName = triples.get(i).getObject().getUriFunctionName();
            ArrayList<String> ObjectFunctionArguments = triples.get(i).getObject().getUriFunctionArguments();

            String uriForObject = computeUriforObject(ObjectFunctionName, ObjectFunctionArguments, ObjectEntity, uriForSubject, nodeValue, element);

            /*object triple*/
            model.add().s(uriForObject).p(Rdf.TYPE).o("crm:" + ObjectEntity);

            if (nodeName.equals("lido:eventType/lido:term")) { //If node name is "lido:eventType/lido:term" the star entity should has specific value
                if (SubjectEntity.equals("E5_Event")) {
                    StarEntity = SubjectEntity;
                    StarEntityUri = uriForSubject;
                } else {
                    StarEntity = ObjectEntity;
                    StarEntityUri = uriForObject;
                }
            }

            model.add().s(uriForSubject).p("http://www.cidoc-crm.org/rdfs/cidoc_crm_v5.0.2.rdfs#" + triples.get(i).getPredicate()).o(uriForObject);
        }
    }

    private String createTripleforSubject(String SubjectFunctionName, ArrayList<String> SubjectFunctionArguments, String SubjectEntity,
            String StarEntity, String StarEntityUri, Element element, Model model, String file) {
        String uriResultSubject = null;
        if (SubjectFunctionName != null && !SubjectEntity.equals("*")) {

            try {
                ArrayList arguments = new ArrayList();
                String[] entity = SubjectEntity.split("_");
                String res = "";
                for (int j = 0; j < entity.length - 1; j++) {
                    res = res + entity[j + 1];
                }
                arguments.add(res);//the first argument in URI creation functions should be the name of CIDOC class.
                if (SubjectFunctionName.equals("uriEvents")) {
                    arguments.add("");
                }

                String arg = null;
                for (int i = 0; i < SubjectFunctionArguments.size(); i++) {
                    try {
                        if (SubjectEntity.equals("E19_Physical_Object")) {
                            NodeList ndL = getArgumentsAbsoluteXpath(file, SubjectFunctionArguments.get(i));
                            if (ndL.getLength() > 0) {
                                arg = ndL.item(0).getNodeValue();
                            } else {
                                arg = null;
                            }
                        } else if (SubjectFunctionName.equals("uriForPlaces")) {
                            if(i == SubjectFunctionArguments.size()-1){
                                Node lastArgumentForPlaces = getArgumentsRelativeXpath(element, SubjectFunctionArguments.get(i));
                                arguments = getPlaceSpaces(lastArgumentForPlaces);
                            }
                        } else {
                            Node argNode = getArgumentsRelativeXpath(element, SubjectFunctionArguments.get(i));
                            if (argNode != null) {
                                arg = argNode.getNodeValue();
                            } else {
                                arg = null;
                            }
                        }

                    } catch (ParserConfigurationException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (SAXException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (IOException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (XPathExpressionException ex) {
                        Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    if (arg != null) {
                        arguments.add(arg);
                    } else {
                        arguments.add("");
                    }
                } //for
                uriResultSubject = callUriFunction(SubjectFunctionName, arguments);

            } catch (ClassNotFoundException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (NoSuchMethodException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalArgumentException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvocationTargetException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            }

            model.add().s(uriResultSubject).p(Rdf.TYPE).o("crm:" + SubjectEntity);

        } else if (SubjectEntity.equals("*")) {
            SubjectEntity = StarEntity;
            uriResultSubject = StarEntityUri;
            model.add().s(StarEntityUri).p(Rdf.TYPE).o("crm:" + StarEntity);
        }

        return uriResultSubject;
    }

    private String computeUriforObject(String ObjectFunctionName, ArrayList<String> ObjectFunctionArguments,
            String ObjectEntity, String uriResultSubject, String node, Element element) {

        String uriResultObject = null;

        if (ObjectFunctionName != null) {
            ArrayList argumentsObject = new ArrayList();

            String[] entity = ObjectEntity.split("_");
            String res = "";
            for (int j = 0; j < entity.length - 1; j++) {
                res = res + entity[j + 1];
            }

            argumentsObject.add(res);

            if (ObjectFunctionName.equals("appellationURI") || ObjectFunctionName.equals("uriEvents")) {
                argumentsObject.add(uriResultSubject);
            } else if (ObjectFunctionName.equals("createLiteral")) {
                argumentsObject.add(element.getNodeName());
            }
            String arg = null;
            for (int i = 0; i < ObjectFunctionArguments.size(); i++) {
                try {
                    Node argNode = getArgumentsRelativeXpath(element, ObjectFunctionArguments.get(i));
                    if (argNode != null) {
                        arg = argNode.getNodeValue();
                    } else {
                        arg = null;
                    }
                } catch (ParserConfigurationException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                } catch (SAXException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IOException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                } catch (XPathExpressionException ex) {
                    Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
                }
                if (arg != null) {
                    argumentsObject.add(arg);
                } else {
                    argumentsObject.add("");
                }
            }
            try {
                uriResultObject = callUriFunction(ObjectFunctionName, argumentsObject);
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (NoSuchMethodException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalArgumentException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvocationTargetException ex) {
                Logger.getLogger(parserOfLidoFile.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
        return uriResultObject;
    }

    public ArrayList getDom() {
        return dom;
    }

    private ArrayList<ElementInfos> getElementInfosforNode(parserOfMapping msd, String node) {
        ArrayList<ElementInfos> elementInfs = new ArrayList<ElementInfos>();
        for (int k = 0; k < msd.extractingDataMapping.size(); k++) {
            if (msd.extractingDataMapping.get(k).getElement().equals(node)) {
                ElementInfos el = msd.extractingDataMapping.get(k).getElementInfos();
                elementInfs.add(el);
            }
        }
        return elementInfs;
    }

    private String callUriFunction(String functionName, ArrayList functionArguments) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        if (functionName.equals("createLiteral")) {/* if functions' name is createLiteral the first argument should be the tag's name*/

            String[] nodeNAme = ((String) functionArguments.get(1)).split(":");
            functionArguments.set(1, nodeNAme[1]);
        }

        int partypessize = functionArguments.size();
        Class cls = Class.forName("URIidevelopment.URIPolicies");
        Class partypes[] = new Class[partypessize];
        for (int i = 0; i <
                partypessize; i++) {
            partypes[i] = Class.forName("java.lang.String");
        }

        Method meth = cls.getMethod(functionName, partypes);
        URIPolicies methobj = new URIPolicies();
        Object arglist[] = new Object[partypessize];
        for (int j = 0; j <
                partypessize; j++) {
            arglist[j] = (String) functionArguments.get(j);
        }

        Object retobj = meth.invoke(methobj, arglist);
        String retval = (String) retobj;
        return retval;

    }

    private Node getArgumentsRelativeXpath(Element element, String xpathExpression) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        NamespaceContext ctx = new NamespaceContext() {

            public String getNamespaceURI(
                    String prefix) {

                String uri = "http://www.lido-schema.org";
                return uri;
            }

            public String getPrefix(
                    String namespaceURI) {
                throw new UnsupportedOperationException("Not supported yet.");
            }

            public Iterator getPrefixes(
                    String namespaceURI) {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        };

        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();

        xpath.setNamespaceContext(ctx);
        XPathExpression expr = xpath.compile(xpathExpression);
        Object result = expr.evaluate(element, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result;

        if (nodes.getLength() > 0) {
            return nodes.item(0);
        } else {
            return null;
        }
    }

    private NodeList getArgumentsAbsoluteXpath(String file, String xpathExpression) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {

        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true);
        DocumentBuilder builder = domFactory.newDocumentBuilder();
        Document document = builder.parse(new File(file));

        NamespaceContext ctx = new NamespaceContext() {

            public String getNamespaceURI(
                    String prefix) {

                String uri = "http://www.lido-schema.org";
                return uri;
            }

            public String getPrefix(
                    String namespaceURI) {
                throw new UnsupportedOperationException("Not supported yet.");
            }

            public Iterator getPrefixes(
                    String namespaceURI) {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        };

        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();

        xpath.setNamespaceContext(ctx);
        XPathExpression expr = xpath.compile(xpathExpression);
        Object result = expr.evaluate(document, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result;

        return nodes;
    }

    private void fileCreation(String folderName, String fileName, String text) throws IOException {
        File file = new File(folderName, fileName);
        boolean exist = file.createNewFile();
        FileWriter fstream = new FileWriter(file);
        BufferedWriter out = new BufferedWriter(fstream);
        out.write(text);
        out.close();
        System.err.println("File created successfully.");
    }

    private ArrayList<String> getPlaceSpaces(Node node) {
        ArrayList<String> places = new ArrayList<String>();

        NamespaceContext ctx = new NamespaceContext() {

            public String getNamespaceURI(String prefix) {

                String uri = "http://www.lido-schema.org";
                return uri;

            }

            public String getPrefix(String namespaceURI) {
                throw new UnsupportedOperationException("Not supported yet.");
            }

            public Iterator getPrefixes(String namespaceURI) {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        };
        try {
            XPathFactory factory = XPathFactory.newInstance();
            XPath xpath = factory.newXPath();
            xpath.setNamespaceContext(ctx);
            XPathExpression expr;

            while (node != null) {
                String placeNamePath = "lido:namePlaceSet/lido:appellationValue/text()";

                expr = xpath.compile(placeNamePath);
                String placeName = (String) expr.evaluate(node, XPathConstants.STRING);
                places.add(placeName);
                String nodePath = "lido:partOfPlace";
                expr = xpath.compile(nodePath);
                node = (Node) expr.evaluate(node, XPathConstants.NODE);

            }
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }
        return places;
    }
}

