/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.autoupdate;

import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.swing.SwingUtilities;
import org.netbeans.updater.UpdateTracking;

import org.openide.ErrorManager;
import org.openide.LifecycleManager;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.Repository;
import org.openide.modules.InstalledFileLocator;
import org.openide.modules.ModuleInfo;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.SharedClassObject;
import org.openide.windows.WindowManager;


/** This singleton class is a skeleton for running autoupdate, it also
 * contains all communication with core of IDE implementation. This
 * communication is handled by org.netbeans.core.UpdateSupport
 * on the side of IDE Core implementation.
 *
 * @author  Petr Hrebejk
 */
class Autoupdater extends Object {

    /** Is the autoupdate running ? */
    private static boolean isRunning = false;

    /** Getter for static property isRunning */
    static boolean isRunning() {
        return isRunning;
    }

    /** Setter for static property isRunning */
    static void setRunning( boolean isRunning ) {
        Autoupdater.isRunning = isRunning;
    }

    /** Restarts IDE in order to run Updater */
    static void restart() {
        LifecycleManager.getDefault().exit();
    }

    /** Installs autoupdate checker */
    static void installUpdateChecker( final Runnable updateChecker ) {
        // XXX probably not ideal but should work for now...
        if (! Boolean.getBoolean("netbeans.full.hack") && ! Boolean.getBoolean("netbeans.close")) { // NOI18N
            // Just wait for GUI to pop up, whatever...
            l = new WindowListener () {
                public void windowOpened(WindowEvent e) {
                    doInstallUpdateChecker (updateChecker);
                }
                public void windowClosing(WindowEvent e) {}
                public void windowClosed(WindowEvent e) {}
                public void windowIconified(WindowEvent e) {}
                public void windowDeiconified(WindowEvent e) {}
                public void windowActivated(WindowEvent e) {}
                public void windowDeactivated(WindowEvent e) {}
            };
            // WindowsAPI is required to be called from AWT thread only
            SwingUtilities.invokeLater (new Runnable () {
                public void run () {
                    WindowManager.getDefault ().getMainWindow ().addWindowListener (l);
                }
            });
        }
    }
    
    private static WindowListener l;
    
    private static void doInstallUpdateChecker (final Runnable updateChecker) {
        // bugfix #43655, postpone the connect to AU till the main window is opened
        RequestProcessor.getDefault().post(updateChecker, 5000);
        WindowManager.getDefault ().getMainWindow ().removeWindowListener (l);
    }
    
    // Try to avoid referring directly to IDESettings.
    // If we can in fact find IDESettings and all appropriate methods, then we
    // use them. This means proxy config etc. will be properly persisted in
    // the system option. If something goes wrong, log it quietly and revert
    // to just setting the system properties (valid just for the session duration).
    
    private static Object settingsInstance;
   
    private static Method mGetProxyType, mSetProxyType, mGetProxyHost, mSetProxyHost, mGetProxyPort, mSetProxyPort;
    
    private static boolean useReflection() {
        initProxyMethodsMaybe();
        return mSetProxyPort != null;
    }

    private static boolean reflectionAlreadyTried = false;
    
    private static synchronized void initProxyMethodsMaybe() {
        if (reflectionAlreadyTried)
            return;
        
        reflectionAlreadyTried = true;
        
        try {
            ClassLoader l = (ClassLoader)Lookup.getDefault().lookup(ClassLoader.class);
            Class clazz = l.loadClass("org.netbeans.core.IDESettings"); // NOI18N
            settingsInstance = SharedClassObject.findObject(clazz, true);
            mGetProxyType = clazz.getMethod ("getProxyType", null); // NOI18N
            mSetProxyType = clazz.getMethod ("setProxyType", new Class[] {Integer.TYPE}); // NOI18N
            mGetProxyHost = clazz.getMethod("getUserProxyHost", null); // NOI18N
            mSetProxyHost = clazz.getMethod("setUserProxyHost", new Class[] {String.class}); // NOI18N
            mGetProxyPort = clazz.getMethod("getUserProxyPort", null); // NOI18N
            mSetProxyPort = clazz.getMethod("setUserProxyPort", new Class[] {String.class}); // NOI18N
        } catch (Exception e) {
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
            // OK, use system properties rather than reflection.
        }
    }

    private static final String PROXY_HOST = "http.proxyHost"; // NOI18N
    private static final String PROXY_PORT = "http.proxyPort"; // NOI18N

    /** Gets proxy usage */
    static int getProxyType () {
        if (useReflection()) {
            try {
                return ((Integer)mGetProxyType.invoke(settingsInstance, new Object[0])).intValue();
            } catch (Exception e) {
                ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
                // return AUTO_DETECT_PROXY as default
                return 1;
            }
        } else {
            // XXX: return AUTO_DETECT_PROXY as default
            return 1;
        }
    }

    /** Gets Proxy Host */
    static String getUserProxyHost() {
        if (useReflection()) {
            try {
                return (String)mGetProxyHost.invoke(settingsInstance, new Object[0]);
            } catch (Exception e) {
                ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
                return null;
            }
        } else {
            return System.getProperty(PROXY_HOST);
        }
    }

    /** Gets Proxy Port */
    static String getUserProxyPort() {
        if (useReflection()) {
            try {
                return (String)mGetProxyPort.invoke(settingsInstance, new Object[0]);
            } catch (Exception e) {
                ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
                return null;
            }
        } else {
            return System.getProperty(PROXY_PORT);
        }
    }

    /** Sets the whole proxy configuration */
    static void setProxyConfiguration (int proxyType, String host, String port) {
        if (useReflection()) {
            try {
                mSetProxyType.invoke (settingsInstance, new Object[] {new Integer (proxyType)});
                mSetProxyHost.invoke (settingsInstance, new Object[] {host});
                mSetProxyPort.invoke (settingsInstance, new Object[] {port});
            } catch (Exception e) {
                ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
            }
        } else {
            // XXX
            if (proxyType == 0) {
                System.setProperty(PROXY_HOST, ""); // NOI18N
                System.setProperty(PROXY_PORT, ""); // NOI18N
            } else {
                System.setProperty(PROXY_HOST, host);
                System.setProperty(PROXY_PORT, port);
            }
        }
    }

    /** Singleton support class for getting directories */
    static class Support extends Object {

        /** Platform dependent file name separator */
        private static final String FILE_SEPARATOR = System.getProperty ("file.separator");
        
        /** Relative name of update directory */
        private static final String UPDATE_DIR = "update"; // NOI18N
        
        /** Relative name of directory where the .NBM files are downloaded */
        private static final String DOWNLOAD_DIR =UPDATE_DIR + FILE_SEPARATOR + "download"; // NOI18N
        
        /** Relative name of directory where the patch files are stored */
        private static final String PATCH_DIR = ModuleUpdate.NBM_LIB + FILE_SEPARATOR + "patches"; // NOI18N

        /** Relative name of key store file */
        private static final String KS_FILE = ModuleUpdate.NBM_CORE + FILE_SEPARATOR + "ide.ks"; // NOI18N        
        
        /** The name of the log file */
        public static final String LOG_FILE_NAME = "update.log"; // NOI18N
        
        /** The name of the install_later file */
        public static final String LATER_FILE_NAME = "install_later.xml"; // NOI18N
        
        /** Relative name of temp directory */
        private static final String UPDATE_TEMP = "autoupdateTemp";
        
        /** File object representing the temp directory */
        private static FileObject tempDir = null;

        /** Disable instantiation */
        private Support() {}

        /** Retruns array of module descriptions of installed modules */
        public static ModuleInfo[] getModuleDescriptions() {
            Collection descs = Lookup.getDefault().lookup(new Lookup.Template(ModuleInfo.class)).allInstances();
            return (ModuleInfo[])descs.toArray(new ModuleInfo[descs.size()]);
        }        

        /** The directory where to download the distribution files of modules */
        public static File getDownloadDirectory (File base) {
            if (base == null) {
                base = new File (System.getProperty ("netbeans.user"));
            }
            File downloadDirectory = new File (base, DOWNLOAD_DIR);
            if ( !downloadDirectory.isDirectory() ) {
                downloadDirectory.mkdirs();
            }

            return downloadDirectory;
        }
        
        /** Gets the central directory of patches */
        public static List/*<File>*/ getPatchDirectories () {
            List patches = new ArrayList ();
            List/*<File>*/ clusters = UpdateTracking.clusters (true);
            assert clusters != null : "Clusters cannot be empty."; // NOI18N
            Iterator it =  clusters.iterator ();
            while (it.hasNext ()) {
                File dir = (File)it.next ();
                assert dir.isDirectory () : "Cluster " + dir + " is directory."; // NOI18N
                patches.add (new File (dir.getPath () + FILE_SEPARATOR + PATCH_DIR));
            }
            return patches;
        }

        /** Gets the central keystore */
        static File getKSFile () {
//            List ksList = new ArrayList ();
//            List/*<File>*/ clusters = UpdateTracking.clusters (true);
//            assert clusters != null : "Clusters cannot be empty."; // NOI18N
//            Iterator it =  clusters.iterator ();
//            while (it.hasNext ()) {
//                File dir = (File)it.next ();
//                assert dir.isDirectory () : "Cluster " + dir + " is directory."; // NOI18N
//                try {
//                    FileObject foDir = FileUtil.toFileObject (FileUtil.normalizeFile (dir));
//                    FileObject ksFO = foDir.getFileObject (KS_FILE);
//                    if (ksFO != null) {
//                        ksList.add (FileUtil.toFile (ksFO));
//                    }
//                } catch (IllegalArgumentException iae) {
//                    assert false : iae;
//                }
//            }
//            File[] res = new File[ksList.size ()];
//            Iterator it2 = ksList.iterator ();
//            int i = 0;
//            while (it2.hasNext ()) {
//                res[i] = (File)it2.next ();
//                i++;
//            }
            File ksFileLocated = InstalledFileLocator.getDefault ().locate (KS_FILE, null, true);
            return ksFileLocated;
        }

        static File getInstall_Later (File root) {
            File file = new File (root.getPath () + FILE_SEPARATOR + DOWNLOAD_DIR + FILE_SEPARATOR + LATER_FILE_NAME);
            return file;
        }
        
        static File getTempCopyFile(FileObject original) {
            File f = null;
            if ( tempDir == null ) {
                FileSystem fs = Repository.getDefault().getDefaultFileSystem();
                tempDir = fs.getRoot().getFileObject( UPDATE_TEMP );
                try {
                    if ( tempDir == null )
                        tempDir = fs.getRoot().createFolder( UPDATE_TEMP );
                } catch ( java.io.IOException ioe ) {
                }
            }
            if ( tempDir != null )
                try {
                    FileObject fo = tempDir.getFileObject( original.getName(), "nbm" ); // NOI18N
                    if ( fo == null )
                        fo = FileUtil.copyFile( original, tempDir, original.getName() );
                    f = FileUtil.toFile( fo );
                } catch ( java.io.IOException ioe ) {
                }
            return f;
        }
        
        static void deleteTempDir() {
            if ( tempDir == null ) {
                FileSystem fs = Repository.getDefault().getDefaultFileSystem();
                tempDir = fs.getRoot().getFileObject( UPDATE_TEMP );
            }
            if ( tempDir != null ) {
                try {
                    tempDir.delete();
                } catch ( java.io.IOException ioe ) {
                }
            }
        }
    }
}
