/*
 * 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.jump.installer;

import com.sun.jump.module.download.JUMPDownloadDescriptor;
import com.sun.jump.common.JUMPContent;
import com.sun.jump.module.installer.JUMPInstallerModule;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;

import com.sun.midp.installer.InvalidJadException;
import com.sun.midp.jump.MIDletApplication;
import com.sun.midp.jump.JumpInit;
import com.sun.midp.jump.midletsuite.MIDletSuiteStorageAccessor;

/**
 * <code>JUMPInstallerModule</code> provides the ability to install
 * content.
 * Installers have to implement this interface. They can optionally derive from
 * {@link com.sun.jump.module.contentstore.JUMPContentStore} in their
 * implementation for flexible storage options and abstractions.
 */
public class MIDLETInstallerImpl implements JUMPInstallerModule {
    
    static JUMPInstallerInterface     installer = null;
    static StorageAccessInterface     suiteStore = null;

    private static String midpProfileKey = "microedition.profiles";
    private static String midpHomeKey    = "sun.midp.home.path";

    public void unload() {
    }
    
    public void load(Map map) {

        // Check for some system properties and set them if there's no default.
        // Need to do this before initializing MIDletSuiteStorageAccessor.
 
        // For "microedition.profiles" value.
        // Set the property with the module configuration data if the value is provided.
        // Else, check if the property is already set, and if not, set default.
        String profilename = (String) map.get(midpProfileKey);
	if (profilename != null) {
            System.setProperty(midpProfileKey, profilename);
        } else {
            profilename = System.getProperty(midpProfileKey);
            if (profilename == null || profilename.equals("") ) {
               System.setProperty(midpProfileKey, "MIDP-2.0"); // Default
            }
	}     

        // For "sun.midp.home.path" value.
        // Set the property with the configuration data if the value is provided.
        // Else, check if the property is already set, and if not, set default.
        String homeDir = (String) map.get(midpHomeKey);
	if (homeDir != null) {
            System.setProperty(midpHomeKey, homeDir);
        } else {
            homeDir = System.getProperty(midpHomeKey);
            if (homeDir == null || homeDir.equals("") ) {
               String javahome = System.getProperty("java.home", ".");
               System.setProperty(midpHomeKey, javahome + "/midp/midp_fb"); // Default
            }
        }

        JumpInit.init();

        installer = new JUMPFileInstaller();

        suiteStore = new MIDletSuiteStorageAccessor();
    }

    /**
     * Install content specified by the given descriptor and location.
     * @return the installed content
     */
    public JUMPContent[] install(URL location, JUMPDownloadDescriptor desc){
         return installOrUpdate(location, desc, false);
    }

    /**
     * Update content from given location
     */
    public void update(JUMPContent content, URL location, JUMPDownloadDescriptor desc) {
         installOrUpdate(location, desc, true);
    }
    
    private JUMPContent[] installOrUpdate(URL location, JUMPDownloadDescriptor desc, 
         boolean isUpdate){

       String path = location.getPath().toLowerCase();

       // below for more details.
       String bundleName = desc.getName();
       if (bundleName != null) {
           // We need to replace spaces because apparently java doesn't like
           // jarfiles with spaces in the name. Any further string substitutions
           // should be done here.
           bundleName = bundleName.replace(' ', '_');
       }

       try {

          Properties prop = desc.getApplications()[0];
          String localJadFile = prop.getProperty("JUMPApplication_localJadUrl");
          String localJarFile = location.getPath();

          int suiteId = 0;

          if (localJadFile != null) {
              suiteId = installer.verifyAndStoreSuite(desc.getObjectURI(),
                                            null,
                                            localJadFile, localJarFile, isUpdate);
                
          } else if (path.endsWith(".jar")) {
              suiteId = installer.verifyAndStoreSuite(desc.getObjectURI(), 
                                            localJarFile, bundleName, isUpdate);
          } else {
              System.err.println("install() failed, path not a jar file: " + location);
              return null;
          }

          // Install succeeded. Gather the installed midlet suite's info 
	  // from suitestore and convert them to a list of JUMPContents.

          JUMPContent[] installed = suiteStore.convertToMIDletApplications(suiteId);

          return installed;

       } catch (Throwable ex) {
          handleInstallerException(ex);   
       }

       return null;

    }

    /**
     * Uninstall content
     */
    public void uninstall(JUMPContent content) {
        MIDletApplication midlet = (MIDletApplication) content;

        suiteStore.remove(midlet.getMIDletSuiteID());
    }
    
    /**
     * Get all installed content
     */
    public JUMPContent[] getInstalled() {

         ArrayList appslist = new ArrayList();
         JUMPContent[] apps;

         int[] suiteIds = suiteStore.getInstalledMIDletSuiteIds();

         for (int i = 0; i < suiteIds.length; i++) {
            apps = suiteStore.convertToMIDletApplications(suiteIds[i]);
            for (int j = 0; j < apps.length; j++) {
                appslist.add(apps[j]);
            }
         }

         return (JUMPContent[])appslist.toArray(new JUMPContent[]{});
    }

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

        String message = null;

        //ex.printStackTrace();

        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();
    }
}
