/*
 * Copyright (C) 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.sdkstats;

import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;

import org.eclipse.jface.preference.PreferenceStore;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

/**
 * Manages persistence settings for DDMS.
 *
 * For convenience, this also stores persistence settings related to the "server stats" ping
 * as well as some ADT settings that are SDK specific but not workspace specific.
 */
public class DdmsPreferenceStore {

    public  final static String PING_OPT_IN = "pingOptIn";          //$NON-NLS-1$
    private final static String PING_TIME   = "pingTime";           //$NON-NLS-1$
    private final static String PING_ID     = "pingId";             //$NON-NLS-1$

    private final static String ADT_USED = "adtUsed";               //$NON-NLS-1$
    private final static String LAST_SDK_PATH = "lastSdkPath";      //$NON-NLS-1$

    /**
     * PreferenceStore for DDMS.
     * Creation and usage must be synchronized on {@code DdmsPreferenceStore.class}.
     * Don't use it directly, instead retrieve it via {@link #getPreferenceStore()}.
     */
    private static volatile PreferenceStore sPrefStore;

    public DdmsPreferenceStore() {
    }

    /**
     * Returns the DDMS {@link PreferenceStore}.
     * This keeps a static reference on the store, so consequent calls will
     * return always the same store.
     */
    public PreferenceStore getPreferenceStore() {
        synchronized (DdmsPreferenceStore.class) {
            if (sPrefStore == null) {
                // get the location of the preferences
                String homeDir = null;
                try {
                    homeDir = AndroidLocation.getFolder();
                } catch (AndroidLocationException e1) {
                    // pass, we'll do a dummy store since homeDir is null
                }

                if (homeDir == null) {
                    sPrefStore = new PreferenceStore();
                    return sPrefStore;
                }

                assert homeDir != null;

                String rcFileName = homeDir + "ddms.cfg";                       //$NON-NLS-1$

                // also look for an old pref file in the previous location
                String oldPrefPath = System.getProperty("user.home")            //$NON-NLS-1$
                    + File.separator + ".ddmsrc";                               //$NON-NLS-1$
                File oldPrefFile = new File(oldPrefPath);
                if (oldPrefFile.isFile()) {
                    FileOutputStream fileOutputStream = null;
                    try {
                        PreferenceStore oldStore = new PreferenceStore(oldPrefPath);
                        oldStore.load();

                        fileOutputStream = new FileOutputStream(rcFileName);
                        oldStore.save(fileOutputStream, "");    //$NON-NLS-1$
                        oldPrefFile.delete();

                        PreferenceStore newStore = new PreferenceStore(rcFileName);
                        newStore.load();
                        sPrefStore = newStore;
                    } catch (IOException e) {
                        // create a new empty store.
                        sPrefStore = new PreferenceStore(rcFileName);
                    } finally {
                        if (fileOutputStream != null) {
                            try {
                                fileOutputStream.close();
                            } catch (IOException e) {
                                // pass
                            }
                        }
                    }
                } else {
                    sPrefStore = new PreferenceStore(rcFileName);

                    try {
                        sPrefStore.load();
                    } catch (IOException e) {
                        System.err.println("Error Loading DDMS Preferences");
                    }
                }
            }

            assert sPrefStore != null;
            return sPrefStore;
        }
    }

    /**
     * Save the prefs to the config file.
     */
    public void save() {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            try {
                prefs.save();
            }
            catch (IOException ioe) {
                // FIXME com.android.dmmlib.Log.w("ddms", "Failed saving prefs file: " + ioe.getMessage());
            }
        }
    }

    // ---- Utility methods to access some specific prefs ----

    /**
     * Indicates whether the ping ID is set.
     * This should be true when {@link #isPingOptIn()} is true.
     *
     * @return true if a ping ID is set, which means the user gave permission
     *              to use the ping service.
     */
    public boolean hasPingId() {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            return prefs != null && prefs.contains(PING_ID);
        }
    }

    /**
     * Retrieves the current ping ID, if set.
     * To know if the ping ID is set, use {@link #hasPingId()}.
     * <p/>
     * There is no magic value reserved for "missing ping id or invalid store".
     * The only proper way to know if the ping id is missing is to use {@link #hasPingId()}.
     */
    public long getPingId() {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            // Note: getLong() returns 0L if the ID is missing so we do that too when
            // there's no store.
            return prefs == null ? 0L : prefs.getLong(PING_ID);
        }
    }

    /**
     * Generates a new random ping ID and saves it in the preference store.
     *
     * @return The new ping ID.
     */
    public long generateNewPingId() {
        PreferenceStore prefs = getPreferenceStore();

        Random rnd = new Random();
        long id = rnd.nextLong();

        synchronized (DdmsPreferenceStore.class) {
            prefs.setValue(PING_ID, id);
            try {
                prefs.save();
            } catch (IOException e) {
                /* ignore exceptions while saving preferences */
            }
        }

        return id;
    }

    /**
     * Returns the "ping opt in" value from the preference store.
     * This would be true if there's a valid preference store and
     * the user opted for sending ping statistics.
     */
    public boolean isPingOptIn() {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            return prefs != null && prefs.contains(PING_OPT_IN);
        }
    }

    /**
     * Saves the "ping opt in" value in the preference store.
     *
     * @param optIn The new user opt-in value.
     */
    public void setPingOptIn(boolean optIn) {
        PreferenceStore prefs = getPreferenceStore();

        synchronized (DdmsPreferenceStore.class) {
            prefs.setValue(PING_OPT_IN, optIn);
            try {
                prefs.save();
            } catch (IOException e) {
                /* ignore exceptions while saving preferences */
            }
        }
    }

    /**
     * Retrieves the ping time for the given app from the preference store.
     * Callers should use {@link System#currentTimeMillis()} for time stamps.
     *
     * @param app The app name identifier.
     * @return 0L if we don't have a preference store or there was no time
     *  recorded in the store for the requested app. Otherwise the time stamp
     *  from the store.
     */
    public long getPingTime(String app) {
        PreferenceStore prefs = getPreferenceStore();
        String timePref = PING_TIME + "." + app;  //$NON-NLS-1$
        synchronized (DdmsPreferenceStore.class) {
            return prefs == null ? 0 : prefs.getLong(timePref);
        }
    }

    /**
     * Sets the ping time for the given app from the preference store.
     * Callers should use {@link System#currentTimeMillis()} for time stamps.
     *
     * @param app The app name identifier.
     * @param timeStamp The time stamp from the store.
     *                   0L is a special value that should not be used.
     */
    public void setPingTime(String app, long timeStamp) {
        PreferenceStore prefs = getPreferenceStore();
        String timePref = PING_TIME + "." + app;  //$NON-NLS-1$
        synchronized (DdmsPreferenceStore.class) {
            prefs.setValue(timePref, timeStamp);
            try {
                prefs.save();
            } catch (IOException ioe) {
                /* ignore exceptions while saving preferences */
            }
        }
    }

    /**
     * True if this is the first time the users runs ADT, which is detected by
     * the lack of the setting set using {@link #setAdtUsed(boolean)}
     * or this value being set to true.
     *
     * @return true if ADT has been used  before
     *
     * @see #setAdtUsed(boolean)
     */
    public boolean isAdtUsed() {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            if (prefs == null || !prefs.contains(ADT_USED)) {
                return false;
            }
            return prefs.getBoolean(ADT_USED);
        }
    }

    /**
     * Sets whether the ADT startup wizard has been shown.
     * ADT sets first to false once the welcome wizard has been shown once.
     *
     * @param used true if ADT has been used
     */
    public void setAdtUsed(boolean used) {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            prefs.setValue(ADT_USED, used);
            try {
                prefs.save();
            } catch (IOException ioe) {
                /* ignore exceptions while saving preferences */
            }
        }
    }

    /**
     * Retrieves the last SDK OS path.
     * <p/>
     * This is just an information value, the path may not exist, may not
     * even be on an existing file system and/or may not point to an SDK
     * anymore.
     *
     * @return The last SDK OS path from the preference store, or null if
     *  there is no store or an empty string if it is not defined.
     */
    public String getLastSdkPath() {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            return prefs == null ? null : prefs.getString(LAST_SDK_PATH);
        }
    }

    /**
     * Sets the last SDK OS path.
     *
     * @param osSdkPath The SDK OS Path. Can be null or empty.
     */
    public void setLastSdkPath(String osSdkPath) {
        PreferenceStore prefs = getPreferenceStore();
        synchronized (DdmsPreferenceStore.class) {
            prefs.setValue(LAST_SDK_PATH, osSdkPath);
            try {
                prefs.save();
            } catch (IOException ioe) {
                /* ignore exceptions while saving preferences */
            }
        }
    }
}
