/**
 * koSession offers observables whose values are tied to the browser session or history:
 *
 *    sessionValue(key)   - an observable preserved across history entries and reloads.
 *
 * Note: we could also support "browserValue", shared across all tabs and across browser restarts
 * (same as sessionValue but using window.localStorage), but it seems more appropriate to store
 * such values on the server.
 */

/* global window, $ */

var _ = require('underscore');
var ko = require('knockout');

/**
 * Maps a string key to an observable. The space of keys is shared for all kinds of observables,
 * and they differ only in where they store their state. Each observable gets several extra
 * properties:
 * @property {String} ksKey The key used for storage. It should be unique across koSession values.
 * @property {Object} ksDefault The default value if the storage doesn't have one.
 * @property {Function} ksFetch The method to fetch the value from storage.
 * @property {Function} ksSave The method to save the value to storage.
 */
var _sessionValues = {};

function createObservable(key, defaultValue, methods) {
  var obs = _sessionValues[key];
  if (!obs) {
    _sessionValues[key] = obs = ko.observable();
    obs.ksKey = key;
    obs.ksDefaultValue = defaultValue;
    obs.ksFetch = methods.fetch;
    obs.ksSave = methods.save;
    obs.dispose = methods.dispose;

    // We initialize the observable before setting rateLimit, to ensure that the initialization
    // doesn't end up triggering subscribers that are about to be added (which seems to be a bit
    // of a problem with rateLimit extender, and possibly deferred). This workaround relies on the
    // fact that the extender modifies its target without creating a new one.
    obs(obs.ksFetch());
    obs.extend({deferred: true});

    obs.subscribe(function(newValue) {
      if (newValue !== this.ksFetch()) {
        console.log("koSession: %s changed %s -> %s", this.ksKey, this.ksFetch(), newValue);
        this.ksSave(newValue);
      }
    }, obs);
  }
  return obs;
}

/**
 * Returns an observable whose value sticks across reloads and navigation, but is different for
 * different browser tabs. E.g. it may be used to reflect whether a side pane is open.
 * The `key` isn't visible to the user, so pick any unique string name.
 */
function sessionValue(key, optDefault) {
  return createObservable(key, optDefault, sessionValueMethods);
}
exports.sessionValue = sessionValue;

var sessionValueMethods = {
  'fetch': function() {
    var value = window.sessionStorage.getItem(this.ksKey);
    if (!value) {
      return this.ksDefaultValue;
    }
    try {
      return JSON.parse(value);
    } catch (e) {
      return this.ksDefaultValue;
    }
  },
  'save': function(value) {
    window.sessionStorage.setItem(this.ksKey, JSON.stringify(value));
  },
  'dispose': function(value) {
    window.sessionStorage.removeItem(this.ksKey);
  }
};

function onApplyState() {
  _.each(_sessionValues, function(obs, key) {
    obs(obs.ksFetch());
  });
}

$(window).on('applyState', onApplyState);