/*
 *
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.midp.scriptutil;

import java.io.*;

import javax.microedition.midlet.MIDlet;

import com.sun.midp.configurator.Constants;

import com.sun.midp.installer.*;

import com.sun.midp.midlet.*;

import com.sun.midp.midletsuite.*;

/**
 * Installs/Updates a suite, prints out the result and parameters to
 * run the first MIDlet of the suite using runMidlet. Upon success
 * write to standard output the suite ID and class name of the first
 * MIDlet enclosed by brackets. ["suiteID" "class name"]
 * <p>
 * The MIDlet uses these application properties as arguments: </p>
 * <ol>
 *   <li>arg-0: URL for the suite
 *   <li>arg-1: Used to override the default domain used when installing
 *    an unsigned suite. The default is maximum to allow the runtime API tests
 *    be performed automatically without tester interaction. The domain name
 *    may be followed by a colon and a list of permissions that must be allowed
 *    even if they are not listed in the MIDlet-Permissions attribute in the
 *    application descriptor file. Instead of the list a keyword "all" can be
 *    specified indicating that all permissions must be allowed, for example:
 *    operator:all.
 * </ol>
 */
public class HttpJadInstaller extends MIDlet implements Runnable {
    /** URL of the test suite. */
    String url;
    /** The installer. */
    HttpInstaller installer;

    /**
     * Create and initialize the MIDlet.
     */
    public HttpJadInstaller() {
        // The arg-<n> properties are generic command arguments
        url = getAppProperty("arg-0");
        if (url == null) {
            System.err.println("URL argument missing.");
            System.err.println("HTTP JAD installer arguments: " +
                "<HTTP URL> [<domain for unsigned suites>]");

            notifyDestroyed();
            return;
        }

        installer = new HttpInstaller();

        // URL given as a argument, look for a domain arg and then start
        String domain = getAppProperty("arg-1");
        if (domain != null) {
            String additionalPermissions = null;
            int index = domain.indexOf(":");
            int len = domain.length();

            if (index > 0 && index + 1 < len) {
                additionalPermissions = domain.substring(index + 1, len);
                domain = domain.substring(0, index);
            }

            installer.setUnsignedSecurityDomain(domain);
            installer.setExtraPermissions(additionalPermissions);
        }

        new Thread(this).start();
    }

    /**
     * Start.
     */
    public void startApp() {
    }

    /**
     * Pause; there are no resources that need to be released.
     */
    public void pauseApp() {
    }

    /**
     * Destroy cleans up.
     *
     * @param unconditional is ignored; this object always
     * destroys itself when requested.
     */
    public void destroyApp(boolean unconditional) {
    }


    /** Install a MIDlet suite. */
    public void run() {
        int suiteId;
        MIDletInfo midletInfo;

        try {
            // force an overwrite and remove the RMS data
            suiteId = installer.installJad(url, Constants.INTERNAL_STORAGE_ID,
                                           true, true, null);
            midletInfo = getFirstMIDletOfSuite(suiteId);

            System.out.println("*Installed suite " + midletInfo.name);
            System.out.println("[" + suiteId + " " +
                               midletInfo.classname + "]");
        } catch (Throwable t) {
            handleInstallerException(t);
        }

        notifyDestroyed();
    }

    /**
     * Returns the information of the first MIDlet of the newly installed
     * suite.
     *
     * @param suiteId ID of the MIDlet Suite
     *
     * @return an object with the class name and display name of
     * the suite's MIDlet-1 property
     */
    static MIDletInfo getFirstMIDletOfSuite(int suiteId) {
        MIDletSuiteStorage midletSuiteStorage =
            MIDletSuiteStorage.getMIDletSuiteStorage();
        MIDletSuite ms = null;
        String name = null;

        try {
            ms = midletSuiteStorage.getMIDletSuite(suiteId, false);
            name = ms.getProperty("MIDlet-1");
        } catch (Exception e) {
            throw new RuntimeException("midlet properties corrupted");
        } finally {
            if (ms != null) {
                ms.close();
            }
        }

        if (name == null) {
            throw new RuntimeException("MIDlet-1 missing");
        }

        // found the entry now parse out the class name, and display name
        return new MIDletInfo(name);
    }


    /**
     * Handles an installer exceptions.
     *
     * @param ex exception to handle
     */
    static void handleInstallerException(Throwable ex) {
        String message = null;

        if (ex instanceof InvalidJadException) {
            InvalidJadException ije = (InvalidJadException)ex;
            int reason = ije.getReason();

            message = "** Error installing suite (" + reason + "): " +
                messageForInvalidJadException(ije);
        } else if (ex instanceof IOException) {
            message = "** I/O Error installing suite: " + ex.getMessage();
        } else {
            message = "** Error installing suite: " + ex.toString();
        }

        if (message != null) {
            System.err.println(message);
        }
    }

    /**
     * Returns the associated message for the given exception.
     * This function is here instead of in the exception its self because
     * it not need on devices, it needed only on development platforms that
     * have command line interface.
     *
     * @param ije reason reason code for the exception
     *
     * @return associated message for the given reason
     */
    static String messageForInvalidJadException(InvalidJadException ije) {
        switch (ije.getReason()) {
        case InvalidJadException.MISSING_PROVIDER_CERT:
        case InvalidJadException.MISSING_SUITE_NAME:
        case InvalidJadException.MISSING_VENDOR:
        case InvalidJadException.MISSING_VERSION:
        case InvalidJadException.MISSING_JAR_URL:
        case InvalidJadException.MISSING_JAR_SIZE:
        case InvalidJadException.MISSING_CONFIGURATION:
        case InvalidJadException.MISSING_PROFILE:
            return "A required attribute is missing";

        case InvalidJadException.SUITE_NAME_MISMATCH:
        case InvalidJadException.VERSION_MISMATCH:
        case InvalidJadException.VENDOR_MISMATCH:
            return "A required suite ID attribute in the JAR manifest " +
                "do not match the one in the JAD";

        case InvalidJadException.ATTRIBUTE_MISMATCH:
            return "The value for " + ije.getExtraData() + " in the " +
                "trusted JAR manifest did not match the one in the JAD";

        case InvalidJadException.CORRUPT_PROVIDER_CERT:
            return "The content provider certificate cannot be decoded.";

        case InvalidJadException.UNKNOWN_CA:
            return "The content provider certificate issuer " +
                ije.getExtraData() + " is unknown.";

        case InvalidJadException.INVALID_PROVIDER_CERT:
            return "The signature of the content provider certificate " +
                "is invalid.";

        case InvalidJadException.CORRUPT_SIGNATURE:
            return "The JAR signature cannot be decoded.";

        case InvalidJadException.INVALID_SIGNATURE:
            return "The signature of the JAR is invalid.";

        case InvalidJadException.UNSUPPORTED_CERT:
            return "The content provider certificate is not a supported " +
                "version.";

        case InvalidJadException.EXPIRED_PROVIDER_CERT:
            return "The content provider certificate is expired.";

        case InvalidJadException.EXPIRED_CA_KEY:
            return "The public key of " + ije.getExtraData() +
                " has expired.";

        case InvalidJadException.JAR_SIZE_MISMATCH:
            return "The Jar downloaded was not the size in the JAD";

        case InvalidJadException.OLD_VERSION:
            return "The application is an older version of one that is " +
                "already installed";

        case InvalidJadException.NEW_VERSION:
            return "The application is an newer version of one that is " +
                "already installed";

        case InvalidJadException.INVALID_JAD_URL:
            return "The JAD URL is invalid";

        case InvalidJadException.JAD_SERVER_NOT_FOUND:
            return "JAD server not found";

        case InvalidJadException.JAD_NOT_FOUND:
            return "JAD not found";

        case InvalidJadException.INVALID_JAR_URL:
            return "The JAR URL in the JAD is invalid: " +
                ije.getExtraData();

        case InvalidJadException.JAR_SERVER_NOT_FOUND:
            return "JAR server not found: " + ije.getExtraData();

        case InvalidJadException.JAR_NOT_FOUND:
            return "JAR not found: " + ije.getExtraData();

        case InvalidJadException.CORRUPT_JAR:
            return "Corrupt JAR, error while reading: " +
                ije.getExtraData();

        case InvalidJadException.INVALID_JAR_TYPE:
            if (ije.getExtraData() != null) {
                return "JAR did not have the correct media type, it had " +
                    ije.getExtraData();
            }

            return "The server did not have a resource with an " +
                "acceptable media type for the JAR URL. (code 406)";

        case InvalidJadException.INVALID_JAD_TYPE:
            if (ije.getExtraData() != null) {
                String temp = ije.getExtraData();

                if (temp.length() == 0) {
                    return "JAD did not have a media type";
                }

                return "JAD did not have the correct media type, it had " +
                    temp;
            }

            /*
             * Should not happen, the accept field is not send
             * when getting the JAD.
             */
            return "The server did not have a resource with an " +
                "acceptable media type for the JAD URL. (code 406)";

        case InvalidJadException.INVALID_KEY:
            return "The attribute key [" + ije.getExtraData() +
                "] is not in the proper format";

        case InvalidJadException.INVALID_VALUE:
            return "The value for attribute " + ije.getExtraData() +
                " is not in the proper format";

        case InvalidJadException.INSUFFICIENT_STORAGE:
            return "There is insufficient storage to install this suite";

        case InvalidJadException.UNAUTHORIZED:
            return "Authentication required or failed";

        case InvalidJadException.JAD_MOVED:
            return "The JAD to be installed is for an existing suite, " +
                "but not from the same domain as the existing one: " +
                ije.getExtraData();

        case InvalidJadException.CANNOT_AUTH:
            return
                "Cannot authenticate with the server, unsupported scheme";

        case InvalidJadException.DEVICE_INCOMPATIBLE:
            return "Either the configuration or profile is not supported.";

        case InvalidJadException.ALREADY_INSTALLED:
            return
                "The JAD matches a version of a suite already installed.";

        case InvalidJadException.AUTHORIZATION_FAILURE:
            return "The suite is not authorized for " + ije.getExtraData();

        case InvalidJadException.PUSH_DUP_FAILURE:
            return "The suite is in conflict with another application " +
                "listening for network data on " + ije.getExtraData();

        case InvalidJadException.PUSH_FORMAT_FAILURE:
            return "Push attribute in incorrectly formated: " +
                ije.getExtraData();

        case InvalidJadException.PUSH_PROTO_FAILURE:
            return "Connection in push attribute is not supported: " +
                ije.getExtraData();

        case InvalidJadException.PUSH_CLASS_FAILURE:
            return "The class in push attribute not in a MIDlet-<n> " +
                "attribute: " + ije.getExtraData();

        case InvalidJadException.TRUSTED_OVERWRITE_FAILURE:
            return "Cannot update a trusted suite with an untrusted " +
                "version";

        case InvalidJadException.INVALID_CONTENT_HANDLER:
	    return "Content handler attribute(s) incorrectly formatted: " +
		ije.getExtraData();

	case InvalidJadException.CONTENT_HANDLER_CONFLICT:
	    return "Content handler would conflict with another handler: " +
		ije.getExtraData();

        case InvalidJadException.CA_DISABLED:
            return "The application can't be authorized because " +
                ije.getExtraData() + " is disabled.";

        case InvalidJadException.UNSUPPORTED_CHAR_ENCODING:
            return "Unsupported character encoding: " + ije.getExtraData();
        }

        return ije.getMessage();
    }
}
