/* //device/tools/ddms/src/com/android/ddms/PrefsDialog.java
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

package com.android.ddms;

import com.android.ddmlib.DdmPreferences;
import com.android.ddmlib.Log;
import com.android.ddmlib.Log.LogLevel;
import com.android.ddmuilib.DdmUiPreferences;
import com.android.ddmuilib.PortFieldEditor;
import com.android.sdkstats.SdkStatsService;

import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.DirectoryFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.FontFieldEditor;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.preference.PreferenceManager;
import org.eclipse.jface.preference.PreferenceNode;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.preference.RadioGroupFieldEditor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;

import java.io.File;
import java.io.IOException;

/**
 * Preferences dialog.
 */
public final class PrefsDialog {

    // Preference store.
    private static PreferenceStore mPrefStore;

    // public const values for storage
    public final static String SHELL_X = "shellX"; //$NON-NLS-1$
    public final static String SHELL_Y = "shellY"; //$NON-NLS-1$
    public final static String SHELL_WIDTH = "shellWidth"; //$NON-NLS-1$
    public final static String SHELL_HEIGHT = "shellHeight"; //$NON-NLS-1$
    public final static String EXPLORER_SHELL_X = "explorerShellX"; //$NON-NLS-1$
    public final static String EXPLORER_SHELL_Y = "explorerShellY"; //$NON-NLS-1$
    public final static String EXPLORER_SHELL_WIDTH = "explorerShellWidth"; //$NON-NLS-1$
    public final static String EXPLORER_SHELL_HEIGHT = "explorerShellHeight"; //$NON-NLS-1$
    public final static String SHOW_NATIVE_HEAP = "native"; //$NON-NLS-1$

    public final static String LOGCAT_COLUMN_MODE = "ddmsLogColumnMode"; //$NON-NLS-1$
    public final static String LOGCAT_FONT = "ddmsLogFont"; //$NON-NLS-1$

    public final static String LOGCAT_COLUMN_MODE_AUTO = "auto"; //$NON-NLS-1$
    public final static String LOGCAT_COLUMN_MODE_MANUAL = "manual"; //$NON-NLS-1$

    private final static String PREFS_DEBUG_PORT_BASE = "adbDebugBasePort"; //$NON-NLS-1$
    private final static String PREFS_SELECTED_DEBUG_PORT = "debugSelectedPort"; //$NON-NLS-1$
    private final static String PREFS_DEFAULT_THREAD_UPDATE = "defaultThreadUpdateEnabled"; //$NON-NLS-1$
    private final static String PREFS_DEFAULT_HEAP_UPDATE = "defaultHeapUpdateEnabled"; //$NON-NLS-1$

    private final static String PREFS_THREAD_REFRESH_INTERVAL = "threadStatusInterval"; //$NON-NLS-1$

    private final static String PREFS_LOG_LEVEL = "ddmsLogLevel"; //$NON-NLS-1$


    /**
     * Private constructor -- do not instantiate.
     */
    private PrefsDialog() {}

    /**
     * Return the PreferenceStore that holds our values.
     */
    public static PreferenceStore getStore() {
        return mPrefStore;
    }

    /**
     * Save the prefs to the config file.
     */
    public static void save() {
        try {
            mPrefStore.save();
        }
        catch (IOException ioe) {
            Log.w("ddms", "Failed saving prefs file: " + ioe.getMessage());
        }
    }

    /**
     * Do some one-time prep.
     *
     * The original plan was to let the individual classes define their
     * own defaults, which we would get and then override with the config
     * file.  However, PreferencesStore.load() doesn't trigger the "changed"
     * events, which means we have to pull the loaded config values out by
     * hand.
     *
     * So, we set the defaults, load the values from the config file, and
     * then run through and manually export the values.  Then we duplicate
     * the second part later on for the "changed" events.
     */
    public static void init() {
        assert mPrefStore == null;
        
        mPrefStore = SdkStatsService.getPreferenceStore();
        
        if (mPrefStore == null) {
            // we have a serious issue here...
            Log.e("ddms",
                    "failed to access both the user HOME directory and the system wide temp folder. Quitting.");
            System.exit(1);
        }

        // configure default values
        setDefaults(System.getProperty("user.home")); //$NON-NLS-1$

        // listen for changes
        mPrefStore.addPropertyChangeListener(new ChangeListener());

        // Now we initialize the value of the preference, from the values in the store.

        // First the ddm lib.
        DdmPreferences.setDebugPortBase(mPrefStore.getInt(PREFS_DEBUG_PORT_BASE));
        DdmPreferences.setSelectedDebugPort(mPrefStore.getInt(PREFS_SELECTED_DEBUG_PORT));
        DdmPreferences.setLogLevel(mPrefStore.getString(PREFS_LOG_LEVEL));
        DdmPreferences.setInitialThreadUpdate(mPrefStore.getBoolean(PREFS_DEFAULT_THREAD_UPDATE));
        DdmPreferences.setInitialHeapUpdate(mPrefStore.getBoolean(PREFS_DEFAULT_HEAP_UPDATE));

        // some static values
        String out = System.getenv("ANDROID_PRODUCT_OUT"); //$NON-NLS-1$
        DdmUiPreferences.setSymbolsLocation(out + File.separator + "symbols"); //$NON-NLS-1$
        DdmUiPreferences.setAddr2LineLocation("arm-eabi-addr2line"); //$NON-NLS-1$

        String traceview = System.getProperty("com.android.ddms.bindir");  //$NON-NLS-1$
        if (traceview != null && traceview.length() != 0) {
            traceview += File.separator + "traceview"; //$NON-NLS-1$
        } else {
            traceview = "traceview"; //$NON-NLS-1$
        }
        DdmUiPreferences.setTraceviewLocation(traceview);

        // Now the ddmui lib
        DdmUiPreferences.setStore(mPrefStore);
        DdmUiPreferences.setThreadRefreshInterval(mPrefStore.getInt(PREFS_THREAD_REFRESH_INTERVAL));
    }

    /*
     * Set default values for all preferences.  These are either defined
     * statically or are based on the values set by the class initializers
     * in other classes.
     *
     * The other threads (e.g. VMWatcherThread) haven't been created yet,
     * so we want to use static values rather than reading fields from
     * class.getInstance().
     */
    private static void setDefaults(String homeDir) {
        mPrefStore.setDefault(PREFS_DEBUG_PORT_BASE, DdmPreferences.DEFAULT_DEBUG_PORT_BASE);

        mPrefStore.setDefault(PREFS_SELECTED_DEBUG_PORT,
                DdmPreferences.DEFAULT_SELECTED_DEBUG_PORT);

        mPrefStore.setDefault(PREFS_DEFAULT_THREAD_UPDATE, true);
        mPrefStore.setDefault(PREFS_DEFAULT_HEAP_UPDATE, false);
        mPrefStore.setDefault(PREFS_THREAD_REFRESH_INTERVAL,
            DdmUiPreferences.DEFAULT_THREAD_REFRESH_INTERVAL);

        mPrefStore.setDefault("textSaveDir", homeDir); //$NON-NLS-1$
        mPrefStore.setDefault("imageSaveDir", homeDir); //$NON-NLS-1$

        mPrefStore.setDefault(PREFS_LOG_LEVEL, "info"); //$NON-NLS-1$

        // choose a default font for the text output
        FontData fdat = new FontData("Courier", 10, SWT.NORMAL); //$NON-NLS-1$
        mPrefStore.setDefault("textOutputFont", fdat.toString()); //$NON-NLS-1$

        // layout information.
        mPrefStore.setDefault(SHELL_X, 100);
        mPrefStore.setDefault(SHELL_Y, 100);
        mPrefStore.setDefault(SHELL_WIDTH, 800);
        mPrefStore.setDefault(SHELL_HEIGHT, 600);

        mPrefStore.setDefault(EXPLORER_SHELL_X, 50);
        mPrefStore.setDefault(EXPLORER_SHELL_Y, 50);

        mPrefStore.setDefault(SHOW_NATIVE_HEAP, false);
    }


    /*
     * Create a "listener" to take action when preferences change.  These are
     * required for ongoing activities that don't check prefs on each use.
     *
     * This is only invoked when something explicitly changes the value of
     * a preference (e.g. not when the prefs file is loaded).
     */
    private static class ChangeListener implements IPropertyChangeListener {
        public void propertyChange(PropertyChangeEvent event) {
            String changed = event.getProperty();

            if (changed.equals(PREFS_DEBUG_PORT_BASE)) {
                DdmPreferences.setDebugPortBase(mPrefStore.getInt(PREFS_DEBUG_PORT_BASE));
            } else if (changed.equals(PREFS_SELECTED_DEBUG_PORT)) {
                DdmPreferences.setSelectedDebugPort(mPrefStore.getInt(PREFS_SELECTED_DEBUG_PORT));
            } else if (changed.equals(PREFS_LOG_LEVEL)) {
                DdmPreferences.setLogLevel((String)event.getNewValue());
            } else if (changed.equals("textSaveDir")) {
                mPrefStore.setValue("lastTextSaveDir",
                    (String) event.getNewValue());
            } else if (changed.equals("imageSaveDir")) {
                mPrefStore.setValue("lastImageSaveDir",
                    (String) event.getNewValue());

            } else {
                Log.v("ddms", "Preference change: " + event.getProperty()
                    + ": '" + event.getOldValue()
                    + "' --> '" + event.getNewValue() + "'");
            }
        }
    }


    /**
     * Create and display the dialog.
     */
    public static void run(Shell shell) {
        assert mPrefStore != null;

        PreferenceManager prefMgr = new PreferenceManager();

        PreferenceNode node, subNode;

        // this didn't work -- got NPE, possibly from class lookup:
        //PreferenceNode app = new PreferenceNode("app", "Application", null,
        //    AppPrefs.class.getName());

        node = new PreferenceNode("client", new ClientPrefs());
        prefMgr.addToRoot(node);

        subNode = new PreferenceNode("panel", new PanelPrefs());
        //prefMgr.addTo(node.getId(), subNode);
        prefMgr.addToRoot(subNode);

        node = new PreferenceNode("device", new DevicePrefs());
        prefMgr.addToRoot(node);

        node = new PreferenceNode("LogCat", new LogCatPrefs());
        prefMgr.addToRoot(node);

        node = new PreferenceNode("app", new AppPrefs());
        prefMgr.addToRoot(node);

        node = new PreferenceNode("stats", new UsageStatsPrefs());
        prefMgr.addToRoot(node);

        PreferenceDialog dlg = new PreferenceDialog(shell, prefMgr);
        dlg.setPreferenceStore(mPrefStore);

        // run it
        dlg.open();

        // save prefs
        try {
            mPrefStore.save();
        }
        catch (IOException ioe) {
        }

        // discard the stuff we created
        //prefMgr.dispose();
        //dlg.dispose();
    }

    /**
     * "Client Scan" prefs page.
     */
    private static class ClientPrefs extends FieldEditorPreferencePage {

        /**
         * Basic constructor.
         */
        public ClientPrefs() {
            super(GRID);        // use "grid" layout so edit boxes line up
            setTitle("Client Scan");
        }

         /**
         * Create field editors.
         */
        @Override
        protected void createFieldEditors() {
            IntegerFieldEditor ife;

            ife = new PortFieldEditor(PREFS_DEBUG_PORT_BASE,
                "ADB debugger base:", getFieldEditorParent());
            addField(ife);

            ife = new PortFieldEditor(PREFS_SELECTED_DEBUG_PORT,
                "Debug selected VM:", getFieldEditorParent());
            addField(ife);
        }
    }

    /**
     * "Panel" prefs page.
     */
    private static class PanelPrefs extends FieldEditorPreferencePage {

        /**
         * Basic constructor.
         */
        public PanelPrefs() {
            super(FLAT);        // use "flat" layout
            setTitle("Info Panels");
        }

        /**
         * Create field editors.
         */
        @Override
        protected void createFieldEditors() {
            BooleanFieldEditor bfe;
            IntegerFieldEditor ife;

            bfe = new BooleanFieldEditor(PREFS_DEFAULT_THREAD_UPDATE,
                "Thread updates enabled by default", getFieldEditorParent());
            addField(bfe);

            bfe = new BooleanFieldEditor(PREFS_DEFAULT_HEAP_UPDATE,
                "Heap updates enabled by default", getFieldEditorParent());
            addField(bfe);

            ife = new IntegerFieldEditor(PREFS_THREAD_REFRESH_INTERVAL,
                "Thread status interval (seconds):", getFieldEditorParent());
            ife.setValidRange(1, 60);
            addField(ife);
        }
    }

    /**
     * "Device" prefs page.
     */
    private static class DevicePrefs extends FieldEditorPreferencePage {

        /**
         * Basic constructor.
         */
        public DevicePrefs() {
            super(FLAT);        // use "flat" layout
            setTitle("Device");
        }

        /**
         * Create field editors.
         */
        @Override
        protected void createFieldEditors() {
            DirectoryFieldEditor dfe;
            FontFieldEditor ffe;

            dfe = new DirectoryFieldEditor("textSaveDir",
                "Default text save dir:", getFieldEditorParent());
            addField(dfe);

            dfe = new DirectoryFieldEditor("imageSaveDir",
                "Default image save dir:", getFieldEditorParent());
            addField(dfe);

            ffe = new FontFieldEditor("textOutputFont", "Text output font:",
                getFieldEditorParent());
            addField(ffe);
        }
    }

    /**
     * "logcat" prefs page.
     */
    private static class LogCatPrefs extends FieldEditorPreferencePage {

        /**
         * Basic constructor.
         */
        public LogCatPrefs() {
            super(FLAT);        // use "flat" layout
            setTitle("Logcat");
        }

        /**
         * Create field editors.
         */
        @Override
        protected void createFieldEditors() {
            RadioGroupFieldEditor rgfe;

            rgfe = new RadioGroupFieldEditor(PrefsDialog.LOGCAT_COLUMN_MODE,
                "Message Column Resizing Mode", 1, new String[][] {
                    { "Manual", PrefsDialog.LOGCAT_COLUMN_MODE_MANUAL },
                    { "Automatic", PrefsDialog.LOGCAT_COLUMN_MODE_AUTO },
                    },
                getFieldEditorParent(), true);
            addField(rgfe);

            FontFieldEditor ffe = new FontFieldEditor(PrefsDialog.LOGCAT_FONT, "Text output font:",
                    getFieldEditorParent());
            addField(ffe);
        }
    }


    /**
     * "Application" prefs page.
     */
    private static class AppPrefs extends FieldEditorPreferencePage {

        /**
         * Basic constructor.
         */
        public AppPrefs() {
            super(FLAT);        // use "flat" layout
            setTitle("DDMS");
        }

        /**
         * Create field editors.
         */
        @Override
        protected void createFieldEditors() {
            RadioGroupFieldEditor rgfe;

            rgfe = new RadioGroupFieldEditor(PREFS_LOG_LEVEL,
                "Logging Level", 1, new String[][] {
                    { "Verbose", LogLevel.VERBOSE.getStringValue() },
                    { "Debug", LogLevel.DEBUG.getStringValue() },
                    { "Info", LogLevel.INFO.getStringValue() },
                    { "Warning", LogLevel.WARN.getStringValue() },
                    { "Error", LogLevel.ERROR.getStringValue() },
                    { "Assert", LogLevel.ASSERT.getStringValue() },
                    },
                getFieldEditorParent(), true);
            addField(rgfe);
        }
    }

    /**
     * "Device" prefs page.
     */
    private static class UsageStatsPrefs extends PreferencePage {

        private BooleanFieldEditor mOptInCheckbox;
        private Composite mTop;

        /**
         * Basic constructor.
         */
        public UsageStatsPrefs() {
            setTitle("Usage Stats");
        }

        @Override
        protected Control createContents(Composite parent) {
            mTop = new Composite(parent, SWT.NONE);
            mTop.setLayout(new GridLayout(1, false));
            mTop.setLayoutData(new GridData(GridData.FILL_BOTH));

            Link text = new Link(mTop, SWT.WRAP);
            text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            text.setText(SdkStatsService.BODY_TEXT);

            text.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent event) {
                    SdkStatsService.openUrl(event.text);
                }
            });

            mOptInCheckbox = new BooleanFieldEditor(SdkStatsService.PING_OPT_IN,
                    SdkStatsService.CHECKBOX_TEXT, mTop);
            mOptInCheckbox.setPage(this);
            mOptInCheckbox.setPreferenceStore(getPreferenceStore());
            mOptInCheckbox.load();

            return null;
        }

        @Override
        protected Point doComputeSize() {
            if (mTop != null) {
                return mTop.computeSize(450, SWT.DEFAULT, true);
            }

            return super.doComputeSize();
        }

        @Override
        protected void performDefaults() {
            if (mOptInCheckbox != null) {
                mOptInCheckbox.loadDefault();
            }
            super.performDefaults();
        }

        @Override
        public void performApply() {
            if (mOptInCheckbox != null) {
                mOptInCheckbox.store();
            }
            super.performApply();
        }

        @Override
        public boolean performOk() {
            if (mOptInCheckbox != null) {
                mOptInCheckbox.store();
            }
            return super.performOk();
        }
    }

}


