/*
 * Copyright (C) 2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the
 * License at http://www.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.packaging;

import com.arsdigita.runtime.*;
import com.arsdigita.util.*;
import com.arsdigita.util.jdbc.*;
import com.arsdigita.util.parameter.*;
import com.arsdigita.xml.XML;
import java.io.*;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
import org.apache.commons.cli.*;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Upgrade
 *
 * @author Justin Ross &lt;jross@redhat.com&gt;
 * @version $Id: //core-platform/dev/src/com/arsdigita/packaging/Upgrade.java#14 $
 */
class Upgrade extends Command {
    public final static String versionId =
        "$Id: //core-platform/dev/src/com/arsdigita/packaging/Upgrade.java#14 $" +
        "$Author: dennis $" +
        "$DateTime: 2004/04/07 16:07:11 $";

    private static final Options s_options = getOptions();

    private String m_from;
    private String m_to;
    private final List m_scripts;

    static {
        s_options.addOption
            (OptionBuilder
             .isRequired()
             .hasArg()
             .withLongOpt("from-version")
             .withDescription("Upgrade from version VERSION")
             .create());
        s_options.addOption
            (OptionBuilder
             .isRequired()
             .hasArg()
             .withLongOpt("to-version")
             .withDescription("Upgrade to version VERSION")
             .create());
        s_options.addOption
            (OptionBuilder
             .hasArg()
             .withLongOpt("parameters")
             .withDescription("Parameters to pass to upgrade scripts")
             .create());
    }

    public Upgrade() {
        super("upgrade",
              "Upgrade a CCM package");

        m_scripts = new ArrayList();
    }

    public boolean run(final String[] args) {
        final CommandLine line;

        if (args.length == 0) {
            usage(s_options, System.err, "PACKAGE-KEY");
            return false;
        }

        try {
            line = new PosixParser().parse(s_options, args);
        } catch (MissingOptionException moe) {
            System.err.println("Missing option " + moe.getMessage());
            usage(s_options, System.err, "PACKAGE-KEY");
            return false;
        } catch (ParseException e) {
            System.err.println(e.getMessage());
            return false;
        }

        if (line.hasOption("usage") || line.hasOption("help")) {
            usage(s_options, System.out, "PACKAGE-KEY");
            return true;
        }

        final String home = System.getProperty("ccm.home");

        if (home == null) {
            System.err.println("ccm.home system property is undefined");
            return false;
        }

        final String[] keys = line.getArgs();

        if (keys.length == 0 || keys.length > 1) {
            usage(s_options, System.err, "PACKAGE-KEY");
            return false;
        }

        final String key = keys[0];
        m_from = line.getOptionValue("from-version");
        m_to = line.getOptionValue("to-version");

        if (m_from == null || m_to == null) {
            usage(s_options, System.err, "PACKAGE-KEY");
            return false;
        }

        final String spec = key + ".upgrade";

        final InputStream in = Thread.currentThread().getContextClassLoader
            ().getResourceAsStream(spec);

        if (in == null) {
            System.err.println("Cannot find " + spec);
            return false;
        }

        XML.parse(in, new Parser());

        if (m_scripts.isEmpty()) {
            System.err.println("No appropriate upgrades found; make sure " +
                               "that your 'to' and 'from' versions match " +
                               "the intended upgrade exactly");
            return false;
        }

        Iterator iter = m_scripts.iterator();

        while (iter.hasNext()) {
            final String[] parts = (String[]) iter.next();
            final String classname = parts[0];
            final String sql = parts[1];

            if (classname != null) {
                final Class clacc = Classes.loadClass(classname);

                System.out.println("Running Java upgrade " + clacc);

                final Method method;

                try {
                    method = clacc.getMethod
                        ("main", new Class[] {String[].class});
                } catch (NoSuchMethodException nsme) {
                    throw new UncheckedWrapperException(nsme);
                } catch (SecurityException se) {
                    throw new UncheckedWrapperException(se);
                }

                String[] params = line.getOptionValues("parameters");
                LinkedList ll = new LinkedList();
                if (params != null) {
                    for (int i = 0; i < params.length; i++) {
                        String[] split = StringUtils.split(params[i],',');
                        for (int j = 0; j < split.length; j++) {
                            ll.add(split[j]);
                        }
                    }
                }
                try {
                    method.invoke(null, new Object[] {ll.toArray(new String[] {})});
                } catch (IllegalAccessException iae) {
                    throw new UncheckedWrapperException(iae);
                } catch (InvocationTargetException ite) {
                    throw new UncheckedWrapperException(ite);
                }
            } else if (sql != null) {
                final SchemaLoader loader = new SchemaLoader(sql);

                System.out.println("Running SQL upgrade " + loader + ", " +
                                   "loaded from the classpath");

                final Connection conn = Connections.acquire
                    (RuntimeConfig.getConfig().getJDBCURL());

                loader.run(conn);

                try {
                    conn.close();
                } catch (SQLException e) {
                    throw new UncheckedWrapperException(e);
                }
            } else {
                throw new IllegalStateException();
            }
        }

        return true;
    }

    private class Parser extends DefaultHandler {
        private String m_version;

        public final void startElement(final String uri,
                                       final String lname,
                                       final String qname,
                                       final Attributes attrs) {
            if (lname.equals("version")) {
                final String from = attrs.getValue(uri, "from");
                final String to = attrs.getValue(uri, "to");

                Assert.exists(from);
                Assert.exists(to);

                m_version = from + "/" + to;
            }

            if (lname.equals("script")) {
                Assert.exists(m_version);
                Assert.exists(m_from);
                Assert.exists(m_to);

                if (m_version.equals(m_from + "/" + m_to)) {
                    final String classname = attrs.getValue(uri, "class");
                    final String sql = attrs.getValue(uri, "sql");

                    if (classname == null && sql == null
                            || classname != null && sql != null) {
                        throw new IllegalArgumentException
                            ("The script element must have a 'class' " +
                             "argument or a 'sql' argument; it may not " +
                             "have both");
                    }

                    m_scripts.add(new String[] {classname, sql});
                }
            }
        }

        public final void endElement(final String uri,
                                     final String lname,
                                     final String qname) {
            if (qname.equals("version")) {
                m_version = null;
            }
        }
    }
}
