/*
 * ConnectionSpecXMLGenerator
 *
 *
 * Copyright (c) 2003 Kansas State University, Laboratory for the Specification,
 * Analysis, and Transformation of Software
 *
 * This software is licensed under the SAnToS Laboratory Open Academic License.  You
 * should have received a copy of the license with the distribution.  A copy can be
 * found at:
 * http://www.cis.ksu.edu/santos/license.html
 * or you can contact the lab at:
 * SAnToS Laboratory
 * 234 Nichols Hall
 * Manhattan, KS 66506, USA
 */
package edu.ksu.cis.cadena.xmlgen;

import edu.ksu.cis.cadena.frontend.ComponentLibrary;
import edu.ksu.cis.cadena.frontend.cad.analysis.DefaultASTVisitor;
import edu.ksu.cis.cadena.frontend.cad.parser.BasicEventConnection;
import edu.ksu.cis.cadena.frontend.cad.parser.Connection;
import edu.ksu.cis.cadena.frontend.cad.parser.CorrelatedEventConnection;
import edu.ksu.cis.cadena.frontend.cad.parser.DataConnection;
import edu.ksu.cis.cadena.frontend.cad.parser.InstanceDecl;
import edu.ksu.cis.cadena.frontend.cad.parser.Scenario;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


/**
 * <p>
 * This class generates the KSU Event Service connection configuration XML file,
 * only BasicEventConnection and CorrelatedEventConnection are considered.
 * DataConnection is not used in this class
 * <p>
 * @version 1.0.3
 * @author Sue Li
 *
 */
public class ConnectionSpecXMLGenerator extends DefaultASTVisitor {
    /** maps instance name to its ID*/
    private Map instanceToIDs; 
    
	/** maps instance name to another map (which maps port name to eventID)*/
    private Map instancePorts; 
    
    /** the next eventID number*/
    private int eventID;
    
    /** the root of Document Of */
    private Document document;
    
    /** the output XML file*/
    private File theFile;
    
    /** the output file writer*/
    private PrintWriter out;
    
	/** the root element*/
    private Element rootElement; 

    /**
     * class constructor initializes variavles
     * 
     * @param directory the directory of output file
     */
    public ConnectionSpecXMLGenerator(String directory) {
        assert directory != null;

        try {
            instancePorts = new HashMap(); //instance names to maps which keep maps the port names to event IDs
            instanceToIDs = new HashMap(); //instance names to IDs
            eventID = 1;
            theFile = new File(directory+"connection.xml");
            out = new PrintWriter(new BufferedWriter(new FileWriter(theFile)));
        } catch (Exception e) {
            System.out.println(e);
        }
    }
    
	/**
	 * gets the instancePorts map
	 * @return a map
	*/
    public Map getInstancePortsMap(){
    	return instancePorts;
    }

    /**
     * Constructor ConnectionSpecXMLGenerator
     */
    public ConnectionSpecXMLGenerator() {
        try {
            instancePorts = new HashMap(); //instance names to maps which keep maps the port names to event IDs
            instanceToIDs = new HashMap(); //instance names to IDs	
            eventID = 1;
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    /**
     * sets the XML file name and creates a new file for the output
     * @param name the output file name
     * @throws IOException
     */
    public void setFileName(String name) {
        assert name != null;

        try {
            theFile = new File(name);
            out = new PrintWriter(new BufferedWriter(new FileWriter(theFile)));
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    /**
     * visits the root of Scenario, generates the root of output file,
     * assign a unique id to each componet instance,
     * starts to visit connections of each instance,
     * tanslates the xml information into file
     *
     * @see buildDom()
     * @see generateXML(PrintWriter)
     * @see visit(DataConnection, Object)
     * @see visit(BasicEventConnection, Object)
     * @see visit(CorrelatedEventConnection, Object)
     *
     * @param node the root of scenario
     * @param input not used here
     * @return null
     */
    public Object visit(Scenario node, Object input) {
        assert node != null;
        buildDOM();

        int componentID = 1;

        for (Iterator iterator = node.getInstanceList().iterator();
                iterator.hasNext();) {

            InstanceDecl instance = (InstanceDecl) iterator.next();

            /*
             * assign a unique componentID to each instance including EventChannel
             * and put the id to instanceToIDs map
             */ 
            int last = instance.getBuildingBlock().trim().lastIndexOf(":") + 1;
            String instanceName =
                instance.getBuildingBlock().trim().substring(last);
            instanceToIDs.put(instance.getName(), new Integer(componentID));

            Map portNames = new HashMap();
            instancePorts.put(instance.getName(), portNames);
            componentID++;
        }

        assert componentID == (node.getInstanceList().size() + 1);

        for (Iterator iterator = node.getInstanceList().iterator();
                iterator.hasNext();) {

            InstanceDecl instance = (InstanceDecl) iterator.next();
            int last = instance.getBuildingBlock().trim().lastIndexOf(":") + 1;
            String instanceName =
                instance.getBuildingBlock().trim().substring(last);

            for (Iterator it = instance.getConnections().iterator();
                    it.hasNext();) {

                Connection c = (Connection) it.next();
                c.apply(this, instance);
            }
        }

        generateXML(out);
        out.close();

        return null;
    }

    /**
     * visits DataConnection, not really used here
     *
     * @param node the DataConnection
     * @param input not used here
     * @return null
     */
    public Object visit(DataConnection node, Object input) {
        assert node != null;
        assert input instanceof InstanceDecl;

        return null;
    }

    /**
     * visits BasicEventConnection,
     * gets the name of service,
     * if the from instance name of this connecion equals to the name of the owner of this connection,
     * builds the connection
     *
     * @see buildConnection(int, String, int, String, String, int, String,String, String)
     *
     * @param node the BasicEventConnection
     * @param input the owner of this connection
     * @return null
     */
    public Object visit(BasicEventConnection node, Object input) {
        assert node != null;
        assert input instanceof InstanceDecl;

        String instanceName = ((InstanceDecl) input).getName();
        String fromName = node.getFromInstance().getName();
        String fromLocation = node.getFromInstance().getLocation().toString();
        String toName = node.getToInstance().getName();
        String toLocation = node.getToInstance().getLocation().toString();

        String service = "KSUES";

        if (node.hasThrough()) {

            String tempService = node.getThrough();

            if (!tempService.trim().equals("")) {
                service = tempService;
            }
        }

        if (fromName.equals(instanceName)) {

            int producerID = ((Integer) instanceToIDs.get(fromName)).intValue();
            int consumerID = ((Integer) instanceToIDs.get(toName)).intValue();
            String eventName = node.getType();
            eventName = getPrefix(eventName);

            String fromPort = node.getFromPort();
            String toPort = node.getToPort();
            Map portMap = (Map) instancePorts.get(fromName);

            if (!portMap.containsKey(fromPort)) {
                portMap.put(fromPort,
                    new PortEntryInformation(eventID, service.equals("CCM"),
                        service.equals("KSUES")));
                buildConnection(producerID, service, 1, eventName, fromPort,
                    eventID, "0", fromName, fromLocation);
                buildConnection(consumerID, service, 2, eventName, toPort,
                    eventID, "0", toName, toLocation);
                eventID++;
            } else {

                PortEntryInformation entry =
                    ((PortEntryInformation) portMap.get(fromPort));
                int id = entry.getEventID();

                boolean noCurrentServiceEntry =
                    (service.equals("CCM") && !entry.hasCCM())
                    || (service.equals("KSUES") && !entry.hasKSUES());

                if (noCurrentServiceEntry) {
                    buildConnection(producerID, service, 1, eventName,
                        fromPort, id, "0", fromName, fromLocation);
                }

                buildConnection(consumerID, service, 2, eventName, toPort, id,
                    "0", toName, toLocation);
            }
        }

        return null;
    }

    /**
     * visits CorrelatedEventConnection,
     * gets the name of service,
     * if this connecion is in a supplier instance, assign an eventID to this connection, builds the supplier part for this connection
     * @consumer part connection in XML file
     *
     * @see buildConnection(int, String, int, String, String, int, String,String, String)
     *
     * @param node the CorrelatedEventConnection
     * @param input the owner of this connection
     * @return null
     */
    public Object visit(CorrelatedEventConnection node, Object input) {
        assert node != null;
        assert input instanceof InstanceDecl;

        String service = "KSUES";

        if (node.hasThrough()) {

            String tempService = node.getThrough();

            if (!tempService.trim().equals("")) {
                service = tempService;
            }
        }

        /*	builds correlated event consumer*/
        if (!node.isIncoming()) {

            String eventType = node.getType();
            eventType = getPrefix(eventType);

            String consumerName = ((InstanceDecl) input).getName();
            String consumerLocation =
                ((InstanceDecl) input).getLocationString();
            String consumerPort = node.getPort();
            int consumerId =
                ((Integer) instanceToIDs.get(consumerName)).intValue();
            buildConnection(consumerId, service, 2, eventType, consumerPort,
                eventID, "0", consumerName, consumerLocation);
            eventID++;
        } else { 			//builds correlated event producer

            String eventTypePro = node.getType();
            eventTypePro = getPrefix(eventTypePro);

            String producerName = node.getInstance().getName();
            String producerLocation = node.getInstance().getLocationString();
            String producerPort = node.getPort();
            int producerId =
                ((Integer) instanceToIDs.get(producerName)).intValue();
            Map portMap = (Map) instancePorts.get(producerName);

            if (!portMap.containsKey(producerPort)) {
                portMap.put(producerPort,
                    new PortEntryInformation(eventID, service.equals("CCM"),
                        service.equals("KSUES")));
                buildConnection(producerId, service, 1, eventTypePro,
                    producerPort, eventID, "0", producerName, producerLocation);
                eventID++;
            } else {

                PortEntryInformation entry =
                    (PortEntryInformation) (portMap.get(producerPort));
                int id = entry.getEventID();
                boolean noCurrentServiceEntry =
                    (service.equals("CCM") && !entry.hasCCM())
                    || (service.equals("KSUES") && !entry.hasKSUES());

                if (noCurrentServiceEntry) {
                    buildConnection(producerId, service, 1, eventTypePro,
                        producerPort, id, "0", producerName, producerLocation);
                }
            }
        }

        return null;
    }

    /**
     * builds the connection in XML file
     *
     * @param id component instance id
     * @param service the tool name that provides the connection service KSUES or other services
     * @param type the role of the event 0 for supplier and 1 for consumer
     * @param eventName the event type name
     * @param portName the port name
     * @param eventID the event ID
     * @param syn synchronziation type 0 for no and 1 for yes
     * @param name component instance name
     * @param location the name of the board where the instance is located on
     */
    private void buildConnection(int id, String service, int type,
        String eventName, String portName, int eventID, String syn,
        String name, String location) {

        Element connection =
            (Element) document.createElement("connection_spec");
        rootElement.appendChild(connection);

        Element comname = (Element) document.createElement("component_name");
        comname.appendChild(document.createTextNode(name.trim()));
        connection.appendChild(comname);

        Element comID = (Element) document.createElement("component_id");
        comID.appendChild(document.createTextNode(String.valueOf(id)));
        connection.appendChild(comID);

        Element serEle = (Element) document.createElement("service");
        serEle.appendChild(document.createTextNode(service));
        connection.appendChild(serEle);

        Element role = (Element) document.createElement("role");
        role.appendChild(document.createTextNode(String.valueOf(type)));
        connection.appendChild(role);

        Element eventname = (Element) document.createElement("event_name");
        eventname.appendChild(document.createTextNode(eventName));
        connection.appendChild(eventname);

        Element eventport = (Element) document.createElement("port_name");
        eventport.appendChild(document.createTextNode(portName));
        connection.appendChild(eventport);

        Element eventid = (Element) document.createElement("event_id");
        eventid.appendChild(document.createTextNode(String.valueOf(eventID)));
        connection.appendChild(eventid);

        Element sync = (Element) document.createElement("sync_id");
        sync.appendChild(document.createTextNode(syn));
        connection.appendChild(sync);

        Element loc = (Element) document.createElement("location");
        loc.appendChild(document.createTextNode(location));
        connection.appendChild(loc);
    }

    /**
     * creates the root of the XML file
     * @throws ParserConfigurationException
     */
    public void buildDOM() {

        try {
            DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.newDocument(); // Create from whole cloth
        } catch (ParserConfigurationException pce) {
            //Parser with specified options can't be built
            pce.printStackTrace();
        }

        rootElement = (Element) document.createElement("configuration");
        document.appendChild(rootElement);
    }

    /**
     * translates the document into XML file
     *
     * @throws TransformerConfigurationException
     * @throws TransformerException
     *
     * @param out the output file
     *
     */
    public void generateXML(PrintWriter out) {

        try {
            TransformerFactory tFactory = TransformerFactory.newInstance();
            Transformer transformer = tFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //set indent true

            DOMSource source = new DOMSource(document);
            StreamResult result = new StreamResult(out);
            transformer.transform(source, result);
        } catch (TransformerConfigurationException tce) {
            System.out.println("* Transformer Factory error");
            System.out.println("* " + tce.getMessage());

            // Use the contained exception, if any	
            Throwable x = tce;

            if (tce.getException() != null) {
                x = tce.getException();
            }

            x.printStackTrace();
        } catch (TransformerException te) {
            System.out.println("* Transformation error");
            System.out.println("  " + te.getMessage());

            //Use the contained exception, if any	
            Throwable x = te;

            if (te.getException() != null) {
                x = te.getException();
            }

            x.printStackTrace();
        }
    }

    /**
     * replaces ":" and "::" by "." and removes any characters before the first letter
     * @param name the string need to be modified
     * @return return a string whose ":" and "::" replaced by "."
     */
    private String getPrefix(String name) {
        assert name != null;

        int beginIndex = 0;

        if (name.startsWith(":")) {
            beginIndex = 2;
        }

        String modalName =
            name.substring(beginIndex, name.indexOf(":", beginIndex));
        ComponentLibrary comLib = ComponentLibrary.getComponentLibrary();
        String prefix = comLib.getPrefix(modalName);
        String postfix =
            edu.ksu.cis.cadena.frontend.cad.parser.ImportLib
            .convertCorbaNameToJava(name);

        if (!prefix.trim().equals("")) {

            return prefix + postfix;
        } else {

            return postfix.substring(1);
        }
    }
}


syntax highlighted by Code2HTML, v. 0.9.1