Source: panels/themes/themes.js

define(function(require) {
  'use strict';

  var AppsCache = require('modules/apps_cache');
  var ManifestHelper = require('shared/manifest_helper');
  var SettingsCache = require('modules/settings_cache');
  var ListView = require('modules/mvvm/list_view');
  var template = require('./layout_template');
  var THEME_SELECTED = 'theme.selected';
  var WALLPAPER_LIST = '/wallpaper.json';
  var WALLPAPER_KEY = 'wallpaper.image';

  var Themes = function ctor_themes() {
    return {
      _container: null,
      _settings: null,
      _selectedTheme: null,
      _previousTheme: null,
      _themes: null,
      _config: {},

      onInit: function th_onInit(panel) {
        this._container = panel.querySelector('.theme-list');
        this._settings = navigator.mozSettings;
      },

      onBeforeShow: function th_onBeforeShow() {
        this._themes = [];
        this.getInstalledThemes((this.setTheme));
      },

      getInstalledThemes: function th_getInstalledThemes(callback) {
        AppsCache.apps().then(function(apps) {
          apps = apps.filter(function(app) {
            var manifest = app.manifest || app.updateManifest;
            return manifest && manifest.type &&
              manifest.type === 'certified' &&
              manifest.role && manifest.role === 'theme';
          });

          for (var app in apps) {
            var manifest = new ManifestHelper(apps[app].manifest);
            var theme = {
              'name': manifest.name,
              'manifestURL': apps[app].manifestURL,
              'onclick': callback.bind(this)
            };
            this._themes.push(theme);
          }

          this._themes.sort(function(a, b) {
            return a.name.localeCompare(b.name);
          });

          this.renderThemes();
        }.bind(this));
      },

      renderThemes: function th_renderThemes() {
        this._listView = ListView(this._container, this._themes, template);
        this.updateRadioButtons();
      },

      updateRadioButtons: function th_updateRadioButtons() {
        var currentSetting = SettingsCache.cache;
        if (!currentSetting) {
          return;
        }
        var theme = this._selectedTheme = currentSetting[THEME_SELECTED];
        this._updateRow(theme, true);
      },

      _doSetTheme: function th_doSetTheme(theme) {
        if (this._selectedTheme === theme) {
          return Promise.resolve();
        }
        return new Promise((function(resolve, reject) {
          var setting = {};
          this._previousTheme = this._selectedTheme;
          setting[THEME_SELECTED] = this._selectedTheme = theme;
          var req = this._settings.createLock().set(setting);
          req.onsuccess = (function() {
            this.getWallpaperPath().
              then((this.loadWallpaper).bind(this)).
              then((this.setWallpaper).bind(this)).
              then((this.saveConfig).bind(this)).then(resolve, reject);
          }).bind(this);
          req.onerror = reject;
        }).bind(this));
      },

      setTheme: function th_setTheme(theme) {
        this.disableSelection();
        return this._doSetTheme(theme).then(this.enableSelection.bind(this),
          this.rollbackTheme.bind(this));
      },

      /**
       * Setting a theme will happen in a two steps process, we will first
       * set the theme manifest, and after that we will save any other setting
       * related to the new theme.
       * If an error happens in any of those two processes, we will need to
       * be sure that we enable back the previous theme selection
       */
      rollbackTheme: function th_rollbackTheme() {
        this._config = {};
        if (this._previousTheme === this._selectedTheme ||
          this._previousTheme === null) {
          return Promise.reject('No previous theme to rollback');
        }
        var previous = '' + this._previousTheme;
        var current = '' + this._selectedTheme;
        return this._doSetTheme(this._previousTheme).then((function() {
          this._updateRow(previous, true);
          this._updateRow(current, false);
          this.enableSelection();
        }).bind(this));
      },

      _updateRow: function th_updateRow(theme, checked) {
        var rule = 'input[value="' + theme + '"]';
        var node = this._container.querySelector(rule);
        if (node) {
          node.checked = !!checked;
        }
      },

      disableSelection: function th_disableSelection() {
        var nodes = this._container.querySelectorAll('input:not([value="' +
         this._selectedTheme + '"])');
        if (nodes) {
          Array.prototype.slice.call(nodes).forEach(function(node) {
            node.parentNode.parentNode.classList.add('disabled');
          });
        }
      },

      enableSelection: function th_enableSelection() {
        var nodes = this._container.querySelectorAll('input');
        if (nodes) {
          Array.prototype.slice.call(nodes).forEach(function(node) {
            node.parentNode.parentNode.classList.remove('disabled');
          });
        }
      },

      /**
       *  Given the current them set, we get the path for the
       *  image set as background.
       *  @returns {Promise} fulfilled with the path to the wallpaper
       */
      getWallpaperPath: function th_getWallpaper() {
        var url = this._selectedTheme
          .substring(0, this._selectedTheme.lastIndexOf('/')) + WALLPAPER_LIST;

        var xhr = new XMLHttpRequest({ mozSystem: true });
        xhr.open('GET', url, true);

        xhr.responseType = 'json';
        return new Promise(function(resolve, reject) {
          xhr.onload = function successGettingWallpaperList() {
            if (xhr.status !== 200) {
              reject(xhr.status);
              return;
            }
            var filename = xhr.response.homescreen;
            resolve(filename);
          };
          xhr.send(null);
        });
      },

      /**
       *  Load an image {blob} from another app via XHR.
       *  @params filename {String} path to the wallpaper on current theme.
       *  @returns {Promise} fulfilled with the blob.
       */
      loadWallpaper: function th_loadWallpaper(filename) {
        if (!filename) {
          return Promise.resolve(null);
        }
        var url = this._selectedTheme.substring(0, this._selectedTheme
          .lastIndexOf('/')) + filename;

        var xhr = new XMLHttpRequest({ mozSystem: true });
        xhr.open('GET', url, true);
        xhr.responseType = 'blob';
        return new Promise(function(resolve, reject) {
          xhr.onload = function() {
            if (xhr.status !== 200) {
              reject(xhr.status);
              return;
            }
            resolve(xhr.response);
          };
          xhr.send(null);
        });
      },

      /**
       *  Saves in memory the configuration for wallpaper. saveConfig
       *  will need to be invoked to make this changes permanent.
       *  @returns {Promise} fulfilled inmediately.
       */
      setWallpaper: function th_setWallpaper(blob) {
        this._config[WALLPAPER_KEY] = blob;
        return Promise.resolve(this._config);
      },

      /**
       * Perform the operation to writing to settings
       * @returns {Promise} fulfilled when config is saved
       */
      saveConfig: function th_saveConfig() {
        var self = this;
        return new Promise(function(resolve, reject) {
          var request = self._settings.createLock().set(self._config);
          request.onerror = reject;
          request.onsuccess = resolve;
        });
      }
    };
  };
  return Themes;
});