package code.loader.parser;

import code.loader.except.ParseException;
import code.loader.except.FileFormatNotSupportException;
import code.instr.Instruction;
import code.symbols.*;
import code.type.*;
import code.modules.CurryModule;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
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 java.io.BufferedReader;
import java.io.IOException;
import java.util.Date;
import java.util.Vector;
import static code.type.TypeFactory.*;

/**
 * This is an old XML parser. It is now (7/28/04) broken and obsolete.
 * Change constructor here to raise an exception if an attempt is made to load XML file.
 *
 * @author Xu Shen
 * @since February 5, 2003
 */
public class XMLParser implements ModuleParser {
    private Document document;
    private String moduleName;
    private Vector imports;
    private Vector allSymbols;
    private Date modifiedDate;
    private int timestamp = 0 ;
    private BufferedReader in = null;

    // public XMLParser() {}

    public void parse(BufferedReader in) {
        reset();
        this.in = in;
        parseDocument();
    }

    private void reset() {
        in = null;
        imports = new Vector();
        allSymbols = new Vector();
    }

    public Vector getImported() { return imports; }

    public Vector getAllSymbols() { return allSymbols; }

    public int getTimestamp() { return timestamp;}

    public boolean isCompiled() { return true; }

    public String getSource() { return null; }

    public String moduleName() { return moduleName; }


    private void parseDocument() {
        DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
        //factory.setValidating(true);
        //factory.setNamespaceAware(true);

        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.parse(new InputSource(in));
        } catch (SAXException sxe) {
            // Error generated during parsing)
            Exception x = sxe;
            if (sxe.getException() != null) {
                x = sxe.getException();
            }
            x.printStackTrace();
        } catch (ParserConfigurationException pce) {
            // Parser with specified options can't be built
            pce.printStackTrace();
        } catch (IOException ioe) {
            // I/O error
            ioe.printStackTrace();
        }

        try {
            parseModuleName();
            parseLastModDate();
            parseImports();
            parseSymbols();
        } catch (Exception e) {
            System.err.println("Exception: Parsing Error: " + e + "!!!");
            System.exit(1);
        }
    }


    private void parseModuleName() throws Exception {
        Element moduleElement =
                (Element) document.getElementsByTagName("Module").item(0);

        moduleName = moduleElement.getAttribute("name");

        System.out.println("MODULE " + moduleName);
    }

    private void parseLastModDate() { modifiedDate = new Date(); }

    private void parseImports() {
        NodeList importElements = document.getElementsByTagName("Import");

        for (int i = 0; i < importElements.getLength(); i++) {
            Element importElement = (Element) importElements.item(i);
            imports.addElement(importElement.getAttribute("moduleName"));

            System.out.println("IMPORT " + importElement.getAttribute("moduleName"));
        }
    }

    private void parseSymbols() throws Exception {
        parseTypes();
        parseConstructors();
        parseOperations();
    }

    private void parseTypes() throws Exception {
        NodeList typeElements = document.getElementsByTagName("Type");

        for (int i = 0; i < typeElements.getLength(); i++) {
            Element typeElement = (Element) typeElements.item(i);
            String typeName = typeElement.getAttribute("name");
            String typeVisibility = typeElement.getAttribute("visibility");
            NodeList typeVariableElements = typeElement.getElementsByTagName("TypeVarialbe");
            TypeVariable[] typeVariables = new TypeVariable[typeVariableElements.getLength()];

            System.out.print("TYPE " + typeName + " " + typeVisibility + " ");

            for (int j = 0; j < typeVariableElements.getLength(); j++) {
                Element typeVariable = (Element) typeVariableElements.item(j);
                typeVariables[j] = (TypeVariable) makeTypeVariable(typeVariable.getAttribute("name"));

                System.out.print(typeVariable.getAttribute("name") + " ");
            }

            System.out.println();

            //Symbol type = new TypeSymbol(moduleName,typeName, typeVariables, Symbol.Public);
            Symbol type = new TypeSymbol(moduleName,typeName, typeVariableElements.getLength(), Symbol.Public);
            // TODO. The above constructor call could be wrong. please check it before using XMLParser.
            // Pravin 8/22/04 
            allSymbols.addElement(type);
        }
    }

    private void parseConstructors() throws Exception {
        NodeList constructorElements = document.getElementsByTagName("Constructor");
        for (int i = 0; i < constructorElements.getLength(); i++) {
            Element constructorElement = (Element) constructorElements.item(i);
            String constructorName = constructorElement.getAttribute("name");
            String visibility = constructorElement.getAttribute("visibility");
            NodeList attrCombo =
                    constructorElement.getElementsByTagName("AttrCombo");
            String infix = null;
            int precedence = -1;
            Element signatureElement = (Element)
                    constructorElement.getElementsByTagName("Signature").item(0);
            TypeExpression signature = null;

            System.out.print("CONSTRUCTOR " + constructorName + " " + visibility);

            if (attrCombo.getLength() == 1) {
                Element attrs = (Element) attrCombo.item(0);
                infix = attrs.getAttribute("infix");
                precedence = Integer.parseInt(attrs.getAttribute("precedence"));

                System.out.print(" " + infix + " " + precedence);
            } else if (attrCombo.getLength() > 1) {
                throw new Exception("More than one element, attrCombo!");
            }

            System.out.print(":: ");

            if (signatureElement != null) {
                signature = parseSignature(signatureElement);
            } else {
                throw new Exception("Cannont find element, Signature!");
            }

            System.out.println();

            Symbol constructor =
                    new ConstructorSymbol(moduleName,constructorName, 0, (byte) 0, (byte) 0, DataSymbol.Left, (byte) precedence, Symbol.Public, signature);

            allSymbols.addElement(constructor);
        }
    }

    private void parseOperations() throws Exception {
        NodeList operationElements = document.getElementsByTagName("Operation");
        for (int i = 0; i < operationElements.getLength(); i++) {
            Element operationElement = (Element) operationElements.item(i);
            String operationName = operationElement.getAttribute("name");
            String visibility = operationElement.getAttribute("visibility");
            NodeList attrCombo =
                    operationElement.getElementsByTagName("AttrCombo");
            String infix = null;
            int precedence = -1;
            Element signatureElement = (Element)
                    operationElement.getElementsByTagName("Signature").item(0);
            TypeExpression signature = null;

            System.out.print("OPERATION " + operationName + " " + visibility);

            if (attrCombo.getLength() == 1) {
                Element attrs = (Element) attrCombo.item(0);
                infix = attrs.getAttribute("infix");
                precedence = Integer.parseInt(attrs.getAttribute("precedence"));

                System.out.print(" " + infix + " " + precedence);
            } else if (attrCombo.getLength() > 1) {
                throw new Exception("More than one element, attrCombo!");
            }

            System.out.print(":: ");

            if (signatureElement != null) {
                signature = parseSignature(signatureElement);
            } else {
                throw new Exception("Cannont find element, Signature!");
            }

            System.out.println();

            Instruction[] code = null;

            Symbol operation =
                    new OperationSymbol(moduleName,operationName, 0, (byte) 0, DataSymbol.Left, (byte) precedence, Symbol.Public, signature, code);

            allSymbols.addElement(operation);
        }
    }


    /**
     * Description of the Method
     *
     * @param signatureElement Description of the Parameter
     * @return Description of the Return Value
     * @throws Exception Description of the Exception
     */
    private TypeExpression parseSignature(Element signatureElement) throws Exception {
        Element typeExpression = (Element)
                signatureElement.getElementsByTagName("TypeExpression").item(0);

        if (typeExpression != null) {
            return (parseTypeExpression(typeExpression));
        } else {
            throw new Exception("Cannont find element, TypeExpression!");
        }
    }

    private TypeExpression parseTypeExpression(Element typeExpressionElement) throws Exception {
        Element nonFunctionTypeExpression = null;
        Element typeExpression = null;
        NodeList children = typeExpressionElement.getChildNodes();

        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeName().equals("NonFunctionTypeExpression")) {
                nonFunctionTypeExpression = (Element) children.item(i);
            }

            if (children.item(i).getNodeName().equals("TypeExpression")) {
                typeExpression = (Element) children.item(i);
            }
        }

        if (typeExpression == null) {
            return parseNonFunctionTypeExpression(nonFunctionTypeExpression);
        } else {
            System.out.print("(");

            TypeExpression domain = parseNonFunctionTypeExpression(nonFunctionTypeExpression);

            System.out.print(")->(");

            TypeExpression range = parseTypeExpression(typeExpression);

            System.out.print(")");

            return makeFunctionType(domain, range);
        }
    }

    private TypeExpression parseNonFunctionTypeExpression(Element nonFunctionTypeExpression)
            throws Exception {
        Vector typeNameRefList = new Vector();
        Vector typeExpressionList = new Vector();
        Vector typeVariableRefList = new Vector();
        NodeList children = nonFunctionTypeExpression.getChildNodes();

        for (int i = 0; i < children.getLength(); i++) {
            String nodeName = children.item(i).getNodeName();

            if (nodeName.equals("TypeNameRef")) {
                typeNameRefList.add(children.item(i));
            }

            if (nodeName.equals("TypeExpression")) {
                typeExpressionList.add(children.item(i));
            }

            if (nodeName.equals("TypeVariableRef")) {
                typeVariableRefList.add(children.item(i));
            }
        }

        int typeExpressionsNum = typeExpressionList.size();

        if (typeNameRefList.size() == 1) {
            String typeName = ((Element) typeNameRefList.elementAt(0)).getAttribute("name");
            TypeConstructor typeConstructor = (TypeConstructor) makeSimpleType(typeName);

            System.out.print(typeName + " ");

            if (typeExpressionsNum <= 0) {
                return typeConstructor;
            }

            TypeVariable[] typeVariables = new TypeVariable[typeExpressionsNum];

            for (int i = 0; i < typeExpressionsNum; i++) {
                typeVariables[i] =
                        (TypeVariable) parseTypeExpression((Element) typeExpressionList.elementAt(i));
            }

            return makeApplicationType(typeConstructor.name, typeVariables);
        } else if (typeNameRefList.size() > 1) {
            throw new Exception("NonFunctionTypeExpression parsing error!");
        }

        if (typeExpressionsNum == 1) {
            return parseTypeExpression((Element) typeExpressionList.elementAt(0));
        }

        if (typeVariableRefList.size() == 1) {
            Element typeVariable = (Element) typeVariableRefList.elementAt(0);

            System.out.print(typeVariable.getAttribute("name"));

            return makeTypeVariable(typeVariable.getAttribute("name"));
        }

        return null;
    }

    /**
     * Stub that should be filled in at later date.
     */
    public void initParser(BufferedReader in) { return; }

}

