package org.ow2.jonas.migration.jboss;

import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;

/**
 * Migrate
 *
 * @author Rafael H. Schloming &lt;rhs@mit.edu&gt;
 **/

public class Migrate extends Transformer {

    private Document m_ejbjar;
    private Document m_jboss;
    private Document m_jbosscmp;

    public Migrate(Document ejbjar, Document jboss, Document jbosscmp) {
        m_ejbjar = ejbjar;
        m_jboss = jboss;
        m_jbosscmp = jbosscmp;
    }

    private Collection getBeanNames(String type) {
        List docs = new ArrayList();
        docs.add(m_ejbjar);
        docs.add(m_jboss);
        docs.add(m_jbosscmp);

        LinkedHashSet result = new LinkedHashSet();
        for (int i = 0; i < docs.size(); i++) {
            Document doc = (Document) docs.get(i);
            Node root = doc.getDocumentElement();
            if (root == null) { continue; }
            result.addAll(values(root,
                                 "enterprise-beans/" + type + "/ejb-name"));
        }
        return result;
    }

    private Node getBean(Document doc, final String name) {
        Node root = doc.getDocumentElement();
        if (root == null) { return null; }
        LinkedHashSet result = new LinkedHashSet();
        String[] paths = {"enterprise-beans/session",
                          "enterprise-beans/entity",
                          "enterprise-beans/message-driven"};
        for (int i = 0; i < paths.length; i++) {
            query(root, paths[i], result, new NodeFilter() {
                public boolean accept(Node node) {
                    return name.equals(value(node, "ejb-name"));
                }
            });
        }
        return singleton(result);
    }

    public void transform() {
        open("jonas-ejb-jar");

        text(LINE);
        text(LINE);
        comment(" this file is autogenerated by jboss2jonas ");
        text(LINE);

        Collection ssn = getBeanNames("session");
        for (Iterator it = ssn.iterator(); it.hasNext(); ) {
            String name = (String) it.next();
            session(name);
        }

        Collection entity = getBeanNames("entity");
        if (!entity.isEmpty()) { text(LINE); }
        for (Iterator it = entity.iterator(); it.hasNext(); ) {
            String name = (String) it.next();
            entity(name);
        }

        Collection message = getBeanNames("message-driven");
        if (!message.isEmpty()) { text(LINE); }
        for (Iterator it = message.iterator(); it.hasNext(); ) {
            String name = (String) it.next();
            message(name);
        }

        Collection relations =
            nodes(m_jbosscmp, "jbosscmp-jdbc/relationships/ejb-relation");
        if (!relations.isEmpty()) { text(LINE); }
        rename(relations, new Mapper()
               .rename("ejb-relation", "jonas-ejb-relation")
               .copy("ejb-relation-name")
               .remove("relation-table-mapping")
               .rename("table-name", "jdbc-table-name")
               .rename("ejb-relationship-role", "jonas-ejb-relationship-role")
               .copy("ejb-relationship-role-name")
               .remove("key-fields")
               .remove("key-field")
               .rename("column-name", "foreign-key-jdbc-name"));
        text(LINE);
        close();
    }

    private void openBean(String type, String name) {
        open("jonas-" + type);
        tag("ejb-name", name);

        Node bean = getBean(m_jboss, name);
        if (bean == null) { return; }

        tag("jndi-name", value(bean, "jndi-name"));
        tag("jndi-local-name", value(bean, "local-jndi-name"));
        tag("run-as", value(bean, "security-identity/run-as-principal"));

        rename(nodes(bean, "resource-ref"), new Mapper()
               .rename("resource-ref", "jonas-resource")
               .copy("res-ref-name")
               .copy("jndi-name")
               .rename("resource-name", "jndi-name")
               .rename("res-url", "jndi-name"));

        rename(nodes(bean, "ejb-ref"), new Mapper()
               .rename("ejb-ref", "jonas-ejb-ref")
               .copy("ejb-ref-name")
               .copy("jndi-name"));

        rename(nodes(bean, "resource-env-ref"), new Mapper()
               .rename("resource-env-ref", "jonas-resource-env")
               .copy("resource-env-ref-name")
               .copy("jndi-name"));
    }

    private void session(String name) {
        openBean("session", name);
        close();
    }

    private String value(Node primary, Node secondary, String path) {
        if (primary == null) { return null; }
        String result = value(primary, path);
        if (result != null) { return result; }
        if (secondary == null) { return null; }
        return value(secondary, path);
    }

    private boolean isTrue(String value, boolean def) {
        if (value == null || value.equals("")) { return def; }
        return value.equalsIgnoreCase("true");
    }

    private String ds2jndi(String ds) {
        if (ds == null) { return "jdbc_1"; }
        if (ds.equals("java:/DefaultDS")) {
            return "jdbc_1";
        } else {
            return ds;
        }
    }

    private void entity(String name) {
        openBean("entity", name);

        Node bean = getBean(m_jbosscmp, name);

        open("jdbc-mapping");
        Node defaults = node(m_jbosscmp, "jbosscmp-jdbc/defaults");
        // XXX: I'm guessing at these defaults
        boolean create = isTrue(value(bean, defaults, "create-table"), true);
        boolean remove = isTrue(value(bean, defaults, "remove-table"), false);
        if (remove) {
            tag("cleanup", "removeall");
        } else if (!create) {
            tag("cleanup", "none");
        }

        tag("jndi-name", ds2jndi(value(bean, defaults, "datasource")));
        tag("jdbc-table-name", value(bean, "table-name"));

        if (bean != null) {
            rename(nodes(bean, "cmp-field"), new Mapper()
                   .rename("cmp-field", "cmp-field-jdbc-mapping")
                   .copy("field-name")
                   .rename("column-name", "jdbc-field-name")
                   // XXX: not sure if this is the right mapping, jonas
                   // sql type may be the same as jboss's jdbc-type
                   .copy("sql-type"));
        }
        close();

        close();
    }

    private void message(String name) {
        openBean("message-driven", name);
        Node bean = getBean(m_jboss, name);
        if (bean != null) {
            String jndi = value(bean, "destination-jndi-name");
            if (!isEmpty(jndi)) {
                open("jonas-message-driven-destination");
                tag("jndi-name", jndi);
                close();
            }
            rename(node(bean, "activation-config"), new Mapper()
                   .copy("activation-config")
                   .copy("activation-config-property")
                   .copy("activation-config-property-name")
                   .copy("activation-config-property-value"));
        }
        close();
    }

    private static final Map ARGS = new LinkedHashMap();

    private static final String arg(String arg, String usage) {
        ARGS.put(arg, usage);
        return arg;
    }

    private static final String HELP =
        arg("--help", "display usage information");
    private static final String EJBJAR =
        arg("--ejb-jar", "the path to the ejb-jar.xml file");
    private static final String JBOSS =
        arg("--jboss", "the path to the jboss.xml file");
    private static final String JBOSSCMP =
        arg("--jbosscmp-jdbc", "the path to the jbosscmp-jdbc.xml file");

    private static void usage(PrintStream out) {
        out.println("Usage: jboss2jonas <options> > jonas-ejb-jar.xml");
        out.println();
        out.println("  This utility can be used to generate a JOnAS specific deployment ");
        out.println("  descriptor based on an ejb-jar file and various JBoss specific ");
        out.println("  configuration files.");
        out.println();
        out.println("Options:");
        for (Iterator it = ARGS.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry me = (Map.Entry) it.next();
            String arg = (String) me.getKey();
            String usage = (String) me.getValue();
            out.println();
            out.println("    " + arg);
            out.println("        " + usage);
        }
    }

    public static final void main(String[] args) {
        List unprocessed = new ArrayList(Arrays.asList(args));
        Map files = new HashMap();
        while (!unprocessed.isEmpty()) {
            String arg = (String) unprocessed.remove(0);
            if (!ARGS.containsKey(arg)) {
                System.err.println("unrecognized arg: " + arg);
                System.exit(1);
            }
            if (arg.equals(HELP)) {
                usage(System.out);
                System.exit(0);
            } else if (unprocessed.isEmpty()) {
                System.err.println(arg + ": requires file");
                System.exit(1);
            }
            files.put(arg, unprocessed.remove(0));
        }
        if (files.size() == 0) {
            usage(System.err);
            System.exit(1);
        }
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db;
        try {
            db = dbf.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        Document ejbjar = parse(db, (String) files.get(EJBJAR));
        Document jboss = parse(db, (String) files.get(JBOSS));
        Document jbosscmp = parse(db, (String) files.get(JBOSSCMP));
        Migrate mig = new Migrate(ejbjar, jboss, jbosscmp);
        mig.transform();
        System.out.println(mig.getDocument().getDocumentElement());
    }

    private static final Document parse(DocumentBuilder db, String file) {
        if (file == null) {
            return db.newDocument();
        } else {
            try {
                return db.parse(new File(file));
            } catch (SAXException e) {
                System.err.println(file + ": " + e.getMessage());
                System.exit(1);
                return null;
            } catch (IOException e) {
                System.err.println(e.getMessage());
                System.exit(1);
                return null;
            }
        }
    }

}
