// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options', function() {

  /////////////////////////////////////////////////////////////////////////////
  // Preferences class:

  /**
   * Preferences class manages access to Chrome profile preferences.
   * @constructor
   */
  function Preferences() {
    // Map of registered preferences.
    this.registeredPreferences_ = {};
  }

  cr.addSingletonGetter(Preferences);

  /**
   * Sets a Boolean preference and signals its new value.
   * @param {string} name Preference name.
   * @param {boolean} value New preference value.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.setBooleanPref = function(name, value, commit, metric) {
    if (!commit) {
      Preferences.getInstance().setPrefNoCommit_(name, 'bool', Boolean(value));
      return;
    }

    var argumentList = [name, Boolean(value)];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('setBooleanPref', argumentList);
  };

  /**
   * Sets an integer preference and signals its new value.
   * @param {string} name Preference name.
   * @param {number} value New preference value.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.setIntegerPref = function(name, value, commit, metric) {
    if (!commit) {
      Preferences.getInstance().setPrefNoCommit_(name, 'int', Number(value));
      return;
    }

    var argumentList = [name, Number(value)];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('setIntegerPref', argumentList);
  };

  /**
   * Sets a double-valued preference and signals its new value.
   * @param {string} name Preference name.
   * @param {number} value New preference value.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.setDoublePref = function(name, value, commit, metric) {
    if (!commit) {
      Preferences.getInstance().setPrefNoCommit_(name, 'double', Number(value));
      return;
    }

    var argumentList = [name, Number(value)];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('setDoublePref', argumentList);
  };

  /**
   * Sets a string preference and signals its new value.
   * @param {string} name Preference name.
   * @param {string} value New preference value.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.setStringPref = function(name, value, commit, metric) {
    if (!commit) {
      Preferences.getInstance().setPrefNoCommit_(name, 'string', String(value));
      return;
    }

    var argumentList = [name, String(value)];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('setStringPref', argumentList);
  };

  /**
   * Sets a string preference that represents a URL and signals its new value.
   * The value will be fixed to be a valid URL when it gets committed to Chrome.
   * @param {string} name Preference name.
   * @param {string} value New preference value.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.setURLPref = function(name, value, commit, metric) {
    if (!commit) {
      Preferences.getInstance().setPrefNoCommit_(name, 'url', String(value));
      return;
    }

    var argumentList = [name, String(value)];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('setURLPref', argumentList);
  };

  /**
   * Sets a JSON list preference and signals its new value.
   * @param {string} name Preference name.
   * @param {Array} value New preference value.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.setListPref = function(name, value, commit, metric) {
    if (!commit) {
      Preferences.getInstance().setPrefNoCommit_(name, 'list', value);
      return;
    }

    var argumentList = [name, JSON.stringify(value)];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('setListPref', argumentList);
  };

  /**
   * Clears the user setting for a preference and signals its new effective
   * value.
   * @param {string} name Preference name.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.clearPref = function(name, commit, metric) {
    if (!commit) {
      Preferences.getInstance().clearPrefNoCommit_(name);
      return;
    }

    var argumentList = [name];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('clearPref', argumentList);
  };

  Preferences.prototype = {
    __proto__: cr.EventTarget.prototype,

    /**
     * Adds an event listener to the target.
     * @param {string} type The name of the event.
     * @param {!Function|{handleEvent:Function}} handler The handler for the
     *     event. This is called when the event is dispatched.
     */
    addEventListener: function(type, handler) {
      cr.EventTarget.prototype.addEventListener.call(this, type, handler);
      if (!(type in this.registeredPreferences_))
        this.registeredPreferences_[type] = {};
    },

    /**
     * Initializes preference reading and change notifications.
     */
    initialize: function() {
      var params1 = ['Preferences.prefsFetchedCallback'];
      var params2 = ['Preferences.prefsChangedCallback'];
      for (var prefName in this.registeredPreferences_) {
        params1.push(prefName);
        params2.push(prefName);
      }
      chrome.send('fetchPrefs', params1);
      chrome.send('observePrefs', params2);
    },

    /**
     * Helper function for flattening of dictionary passed via fetchPrefs
     * callback.
     * @param {string} prefix Preference name prefix.
     * @param {object} dict Map with preference values.
     * @private
     */
    flattenMapAndDispatchEvent_: function(prefix, dict) {
      for (var prefName in dict) {
        if (typeof dict[prefName] == 'object' &&
            !this.registeredPreferences_[prefix + prefName]) {
          this.flattenMapAndDispatchEvent_(prefix + prefName + '.',
              dict[prefName]);
        } else {
          var event = new Event(prefix + prefName);
          this.registeredPreferences_[prefix + prefName].orig = dict[prefName];
          event.value = dict[prefName];
          this.dispatchEvent(event);
        }
      }
    },

    /**
     * Sets a preference and signals its new value. The change is propagated
     * throughout the UI code but is not committed to Chrome yet. The new value
     * and its data type are stored so that commitPref() can later be used to
     * invoke the appropriate set*Pref() method and actually commit the change.
     * @param {string} name Preference name.
     * @param {string} type Preference data type.
     * @param {*} value New preference value.
     * @private
     */
    setPrefNoCommit_: function(name, type, value) {
      var pref = this.registeredPreferences_[name];
      pref.action = 'set';
      pref.type = type;
      pref.value = value;

      var event = new Event(name);
      // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
      event.value = {
        value: value,
        recommendedValue: pref.orig.recommendedValue,
        disabled: pref.orig.disabled,
        uncommitted: true,
      };
      this.dispatchEvent(event);
    },

    /**
     * Clears a preference and signals its new value. The change is propagated
     * throughout the UI code but is not committed to Chrome yet.
     * @param {string} name Preference name.
     * @private
     */
    clearPrefNoCommit_: function(name) {
      var pref = this.registeredPreferences_[name];
      pref.action = 'clear';
      delete pref.type;
      delete pref.value;

      var event = new Event(name);
      // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
      event.value = {
        value: pref.orig.recommendedValue,
        controlledBy: 'recommended',
        recommendedValue: pref.orig.recommendedValue,
        disabled: pref.orig.disabled,
        uncommitted: true,
      };
      this.dispatchEvent(event);
    },

    /**
     * Commits a preference change to Chrome and signals the new preference
     * value. Does nothing if there is no uncommitted change.
     * @param {string} name Preference name.
     * @param {string} metric User metrics identifier.
     */
    commitPref: function(name, metric) {
      var pref = this.registeredPreferences_[name];
      switch (pref.action) {
        case 'set':
          switch (pref.type) {
            case 'bool':
              Preferences.setBooleanPref(name, pref.value, true, metric);
              break;
            case 'int':
              Preferences.setIntegerPref(name, pref.value, true, metric);
              break;
            case 'double':
              Preferences.setDoublePref(name, pref.value, true, metric);
              break;
            case 'string':
              Preferences.setStringPref(name, pref.value, true, metric);
              break;
            case 'url':
              Preferences.setURLPref(name, pref.value, true, metric);
              break;
            case 'list':
              Preferences.setListPref(name, pref.value, true, metric);
              break;
          }
          break;
        case 'clear':
          Preferences.clearPref(name, true, metric);
          break;
      }
      delete pref.action;
      delete pref.type;
      delete pref.value;
    },

    /**
     * Rolls back a preference change and signals the original preference value.
     * Does nothing if there is no uncommitted change.
     * @param {string} name Preference name.
     */
    rollbackPref: function(name) {
      var pref = this.registeredPreferences_[name];
      if (!pref.action)
        return;

      delete pref.action;
      delete pref.type;
      delete pref.value;

      var event = new Event(name);
      event.value = pref.orig;
      event.value.uncommitted = true;
      this.dispatchEvent(event);
    }
  };

  /**
   * Callback for fetchPrefs method.
   * @param {object} dict Map of fetched property values.
   */
  Preferences.prefsFetchedCallback = function(dict) {
    Preferences.getInstance().flattenMapAndDispatchEvent_('', dict);
  };

  /**
   * Callback for observePrefs method.
   * @param {array} notification An array defining changed preference values.
   * notification[0] contains name of the change preference while its new value
   * is stored in notification[1].
   */
  Preferences.prefsChangedCallback = function(notification) {
    var event = new Event(notification[0]);
    event.value = notification[1];
    prefs = Preferences.getInstance();
    prefs.registeredPreferences_[notification[0]] = {orig: notification[1]};
    prefs.dispatchEvent(event);
  };

  // Export
  return {
    Preferences: Preferences
  };

});