/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * OpenStack specific configuration discovery.
 */
angular.module('openstack', ['ngCookies', 'ngResource']);

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * This resource acts as a resource-like service that provides cloud api configurations
 * from various optional sources. First, you may hardcode a configuration using
 * $$configurationProvider.$addConfig. Secondly, you may use the default configuration
 * as registered by peer libraries using $$configurationProvider.$registerDefault.
 * Thirdly, it can load a configuration from ./config.json. Lastly, it provides
 * a simple CRUD interface by which a user may manipulate configurations that
 * are persisted in a browser's persistent storage.
 *
 * An example configuration file follows:
 *
 * [{
 *     "name": "My Little Cloud",
 *     "ironic": {
 *          "apiRoot": "https://ironic.example.com:6385/"
 *     },
 *     "glance": {
 *          "apiRoot": "https://glance.example.com:9292/"
 *     }
 * }]
 */
angular.module('openstack').provider('$$configuration',
  function() {
    'use strict';

    var merge = angular.merge;
    var forEach = angular.forEach;

    /**
     * Promise variables, used to keep certain result sets in state.
     */
    var deferAll, deferConfig, deferStatic, deferDefault, deferLocal;

    /**
     * ID key for the persistent storage.
     *
     * @type {string}
     */
    var storageKey = '$$configuration';

    /**
     * Internal flag: Is the default configuration enabled?
     *
     * @type {boolean} True if enabled, otherwise false.
     */
    var enableDefault = false;

    /**
     * Internal flag: Is configuration loading enabled?
     *
     * @type {boolean} True if enabled, otherwise false.
     */
    var enableConfigLoad = false;

    /**
     * Internal flag: Is persisting via localStorage enabled?
     *
     * @type {boolean} True if enabled, otherwise false.
     */
    var enableLocalStorage = false;

    /**
     * A list of static configurations, added to the provider.
     *
     * @type {Array}
     */
    var staticConfigs = [];

    /**
     * The default configuration instance.
     *
     * @type {{}}
     */
    var defaultConfig = {
      'id': 'default',
      'name': 'Default',
      'source': 'default'
    };

    /**
     * Add a new configuration to the provider. Use this method if you are providing your
     * own configuration loading mechanism during application initialization, such as a
     * hardcoded configuration. For a user-provided configuration, we recommend you use
     * $$configuration.create({}); instead.
     *
     * @param {*} configuration The configuration to add.
     * @returns {void}
     */
    this.$addConfig = function(configuration) {
      configuration = angular.copy(configuration);
      configuration.source = 'static';
      staticConfigs.push(configuration);

      // Reset associated promises
      if (deferStatic) {
        deferStatic = null;
        deferAll = null;
      }
    };

    /**
     * Register a service's default API URL. This allows peer libraries, such as glance-apilib
     * or ironic-apilib, to set a 'default' location for their API.
     *
     * @param {String} serviceName The name of the service to register. Example: 'ironic'.
     * @param {String} serviceUrl The root url of the API.
     * @returns {void}
     */
    this.$registerDefault = function(serviceName, serviceUrl) {
      defaultConfig[serviceName] = {
        'id': serviceName,
        'apiRoot': serviceUrl
      };

      if (deferDefault) {
        deferDefault = null;
        deferAll = null;
      }
    };

    /**
     * Enable, or disable, the default configuration mechanism. This mechanism assumes that peer
     * libraries, such as ironic-jslib or others, register an expected 'default' API root during
     * application initialization. These are provided under the 'default' id.
     *
     * This feature is disabled by default, enable it if you want your UI to permit automatic
     * creation of default configurations.
     *
     * @param {Boolean} enable Whether to enable the default configuration mechanism.
     * @returns {void}
     */
    this.$enableDefault = function(enable) {
      enable = !!enable;
      // Only change things if things have changed
      if (enable !== enableDefault) {
        enableDefault = enable;

        // Reset associated promises
        if (deferDefault) {
          deferDefault = null;
          deferAll = null;
        }
      }
    };

    /**
     * Enable, or disable, the configuration loading mechanism. This mechanism attempts to load
     * a list of cloud configurations from a ./config.json file that lives adjacent to your user
     * interface. This feature is disabled by default, enable it if you want to configure your
     * cloud with a static config.json file.
     *
     * @param {Boolean} enable Whether to enable configuration loading.
     * @returns {void}
     */
    this.$enableConfigLoad = function(enable) {
      enable = !!enable;
      // Only change things if things have changed
      if (enable !== enableConfigLoad) {
        enableConfigLoad = enable;

        // Reset associated promises
        if (deferConfig) {
          deferConfig = null;
          deferAll = null;
        }
      }
    };

    /**
     * Enable, or disable, loading cloud configuration from a browser's LocalStorage. This may
     * be used either to drive a user-provided configuration UI, or to read a configuration that
     * is shared between applications on the same domain.
     *
     * @param {Boolean} enable Whether to enable localStorage configurations.
     * @returns {void}
     */
    this.$enableLocalStorage = function(enable) {
      enable = !!enable;
      // Only change things if things have changed
      if (enable !== enableLocalStorage) {
        enableLocalStorage = enable;

        // Reset associated promises
        if (deferLocal) {
          deferLocal = null;
          deferAll = null;
        }
      }
    };

    /**
     * Create a shallow copy of an object without persisting private methods.
     *
     * @param {*} src The source object.
     * @returns {*|{}} A copy of the object.
     */
    function cleanCopy (src) {
      var dst = {};

      for (var key in src) {
        if (src.hasOwnProperty(key) && !(key.charAt(0) === '$')) {
          dst[key] = src[key];
        }
      }

      return dst;
    }

    this.$get = function($q, $$persistentStorage, $log, $http) {

      /**
       * Store a list of data in local persistent storage.
       *
       * @param {*} list An array of config objects.
       * @returns {void}
       */
      function saveLocal (list) {
        $$persistentStorage.set(storageKey, list);
        deferLocal = null;
        deferAll = null;
      }

      /**
       * Retrieve all configurations from the browser local storage, if enabled.
       *
       * @returns {promise} A resolving promise for local configuration objects.
       */
      function resolveLocal () {
        if (!deferLocal) {
          deferLocal = $q.defer();
          if (enableLocalStorage) {
            var configs = $$persistentStorage.get(storageKey) || [];
            forEach(configs, function(config) {
              config.source = 'local';
            });
            deferLocal.resolve(configs);
          } else {
            deferLocal.resolve([]);
          }
        }
        return deferLocal.promise;
      }

      /**
       * Retrieve all configurations from our various storage mechanisms.
       *
       * @returns {promise} A resolving promise for all configuration objects.
       */
      function resolveAll () {
        // Only trigger this if the promise has been cleared.
        if (!deferAll) {
          deferAll = $q.defer();

          $q.all({
            'local': resolveLocal(),
            'config': resolveConfig(),
            'default': resolveDefault(),
            'static': resolveStatic()
          }).then(function(results) {
            var list = [];
            forEach(results.local, function(config) {
              list.push(config);
            });
            forEach(results.config, function(config) {
              list.push(config);
            });
            forEach(results.default, function(config) {
              list.push(config);
            });
            forEach(results.static, function(config) {
              list.push(config);
            });
            deferAll.resolve(list);
          });
        }

        return deferAll.promise;
      }

      /**
       * Resolve configuration files from the ./config.json file.
       *
       * @returns {promise} A resolving promise for configFile configuration objects.
       */
      function resolveConfig () {
        if (!deferConfig) {
          deferConfig = $q.defer();

          if (!enableConfigLoad) {
            deferConfig.resolve([]);
          } else {
            $log.info('Attempting to load parameters from ./config.json');
            $http.get('./config.json').then(
              function(response) {

                // The result must be an array.
                if (!angular.isArray(response.data)) {
                  $log.warn('Content of ./config.json must be a valid JSON array.');
                  response.data = [];
                }

                forEach(response.data, function(config) {
                  config.source = 'host';
                });

                deferConfig.resolve(response.data);
              },
              function() {
                $log.warn('Cannot load ./config.json, using defaults.');
                deferConfig.resolve([]);
              }
            );
          }
        }
        return deferConfig.promise;
      }

      /**
       * Resolve the default configuration.
       *
       * @returns {promise} A resolving promise for default API calls.
       */
      function resolveDefault () {
        if (!deferDefault) {
          deferDefault = $q.defer();
          if (enableDefault) {
            deferDefault.resolve([defaultConfig]);
          } else {
            deferDefault.resolve([]);
          }
        }

        return deferDefault.promise;
      }

      /**
       * Resolve a list of configurations added to the services at config time.
       *
       * @returns {promise} A resolving promise of static configuration objects.
       */
      function resolveStatic () {
        if (!deferStatic) {
          deferStatic = $q.defer();
          deferStatic.resolve(staticConfigs);
        }
        return deferStatic.promise;
      }

      /**
       * Create a new local configuration. This requires that localStorage is enabled.
       *
       * @param {{}} newConfig The configuration to create.
       * @returns {{}} A resource for this new configuration object.
       */
      function createConfig (newConfig) {
        // Create a result object
        var deferred = $q.defer();

        // Decorate the result resource with necessary bits.
        newConfig = resourceify(newConfig, deferred.promise);

        // We must have local storage enabled.
        if (!enableLocalStorage) {
          deferred.reject('local_storage_disabled');
          return newConfig;
        }

        // The user must provide an id property.
        if (!newConfig.id) {
          deferred.reject('no_id_provided');
          return newConfig;
        }

        // Resolve both all and local.
        $q.all({
          'all': resolveAll(),
          'local': resolveLocal()
        }).then(function(results) {
          // Check for duplicate ID's, reject if one exists.
          for (var i = 0; i < results.all.length; i++) {
            if (newConfig.id === results.all[i].id) {
              deferred.reject('duplicate_id');
              return;
            }
          }

          // Add the new config.
          results.local.push(cleanCopy(newConfig));

          // Stash the results
          saveLocal(results.local);

          // resolve and exit
          deferred.resolve(newConfig);
        });

        return newConfig;
      }

      /**
       * Update a new locally stored configuration.
       *
       * @param {{}} config The configuration object to update.
       * @returns {{}} The updated configuration.
       */
      function updateConfig (config) {
        var deferred = $q.defer();
        config = resourceify(config, deferred.promise);

        // We must have local storage enabled.
        if (!enableLocalStorage) {
          deferred.reject('local_storage_disabled');
          return config;
        }

        // Load local configurations
        resolveLocal().then(
          function(results) {
            // Try to find the matching id.
            for (var i = 0; i < results.length; i++) {
              var storedConfig = results[i];
              if (config.id === storedConfig.id) {
                // Merge the new values onto the stored config.
                storedConfig = merge(storedConfig, cleanCopy(config));

                // Stash the results
                saveLocal(results);

                // Resolve the promise
                deferred.resolve(config);
                return;
              }
            }
            deferred.reject('not_found');
          });

        return config;
      }

      /**
       * Retrieve a configuration by ID.
       *
       * @param {{}} config The configuration object to resolve.
       * @returns {{}} The configuration resource.
       */
      function readConfig (config) {
        var deferred = $q.defer();
        config = resourceify(config, deferred.promise);

        // Force the user to provide an ID.
        if (!config.id) {
          deferred.reject('no_id_provided');
          return config;
        }

        // Load everything, then...
        resolveAll().then(
          function(results) {
            // Try to find the matching id.
            for (var i = 0; i < results.length; i++) {
              var storedConfig = results[i];
              if (config.id === storedConfig.id) {
                // Merge values onto the config.
                merge(config, cleanCopy(storedConfig));
                deferred.resolve(config);
                return;
              }
            }
            deferred.reject('not_found');
          });

        return config;
      }

      /**
       * Remove a locally stored configuration from the cache.
       *
       * @param {{}} config The configuration object to remove.
       * @returns {{}} The configuration object, with appropriate promises.
       */
      function removeConfig (config) {
        var deferred = $q.defer();
        config = resourceify(config, deferred.promise);

        // We must have local storage enabled.
        if (!enableLocalStorage) {
          deferred.reject('local_storage_disabled');
          return config;
        }

        // Load local configurations
        resolveLocal().then(
          function(results) {
            // Try to find the matching id.
            for (var i = 0; i < results.length; i++) {
              var storedConfig = results[i];
              if (storedConfig.id === config.id) {

                // Remove the config from local storage.
                results.splice(i, 1);

                // Stash the results
                saveLocal(results);

                // Resolve the promise and exit
                deferred.resolve(config);
                return;
              }
            }
            deferred.reject('not_found');
          });
        return config;
      }

      /**
       * This method decorates a raw resource with manipulation methods like $delete, $update,
       * etc. These convenience methods permit individual instance manipulation.
       *
       * @param {{}} instance The instance to decorate.
       * @param {Promise} promise The promise to apply.
       *
       * @returns {{}} A clone of the instance, with $ methods added.
       */
      function resourceify (instance, promise) {
        instance.$promise = promise;

        // Set the initial resolved, and keep it up to date.
        instance.$resolved = false;
        instance.$promise.then(function() {
          instance.$resolved = true;
        }, function() {
          instance.$resolved = true;
        });

        instance.$remove = function() {
          return removeConfig(instance);
        };
        instance.$create = function() {
          return createConfig(instance);
        };
        instance.$update = function() {
          return updateConfig(instance);
        };
        instance.$read = function() {
          return readConfig(instance);
        };
        return instance;
      }

      return {
        /**
         * Get a list of all configurations.
         *
         * @returns {{}} A list of configurations.
         */
        'query': function() {
          // Start with the promise
          var listDeferred = $q.defer();

          // Build a resource
          var list = resourceify([], listDeferred.promise);

          // Resolve all the resources, then resolve the list promise.
          resolveAll().then(
            function(results) {
              // Load cloned configs into the result array.
              forEach(results, function(config) {
                var rDeferred = $q.defer();
                var resource = resourceify(cleanCopy(config), rDeferred.promise);
                listDeferred.promise.then(
                  function() {
                    rDeferred.resolve(resource);
                  });
                list.push(resource);
              });

              listDeferred.resolve(list);
            });
          return list;
        },

        /**
         * Create a new configuration.
         *
         * @param {{}} configuration The configuration to add.
         * @returns {{}} A resource for this new configuration object.
         */
        'create': function(configuration) {
          return createConfig(configuration);
        },

        /**
         * Retrieve a specific configuration by ID.
         *
         * @param {String} id The configuration ID/Name to load.
         * @returns {{}} The configuration resource.
         */
        'read': function(id) {
          return readConfig({'id': id});
        },

        /**
         * Update a configuration in the loaded cache. This will error if the user attempts to
         * update a configuration from a static provider - say the config file.
         *
         * @param {{}} configuration The configuration object to update.
         * @returns {{}} The updated configuration.
         */
        'update': function(configuration) {
          return updateConfig(configuration);
        },

        /**
         * Remove a particular configuration from the configuration list.
         *
         * @param {String} id The ID to remove.
         * @returns {{}} The configuration object, with appropriate promises.
         */
        'remove': function(id) {
          return removeConfig({'id': id});
        }
      };
    };
  }
);

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * The $$dummyResource component is a placeholder, which simulates the ngResource API. It does
 * nothing on its own, and should only be used in situations where a more appropriate resource
 * could not be resolved.
 */
angular.module('openstack').factory('$$dummyResource',
  function($q) {
    'use strict';

    /**
     * Apply common resource-like behavior, and instantly reject the promise.
     *
     * @param {*} resource An object to decorate.
     * @returns {*} The decorated instance.
     */
    function resourceify (resource) {
      var deferred = $q.defer();
      resource.$promise = deferred.promise;
      resource.$resolved = false;

      resource.$promise.finally(function() {
        resource.$resolved = true;
      });
      deferred.reject('dummy_resource');
      return resource;
    }

    return {
      'query': function() {
        return resourceify([]);
      },
      'create': function() {
        return resourceify({});
      },
      'read': function() {
        return resourceify({});
      },
      'update': function() {
        return resourceify({});
      },
      'remove': function() {
        return resourceify({});
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * An application-wide static constant that contains error code constants.
 * Different modules and components may dynamically add error codes that serve
 * their own needs.
 */
angular.module('openstack').constant('$$errorCode', {});

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * The resource cache acts as a centralized location where different services' resource
 * instances may be stored. It plays a central role in multi-cloud environments, as a resource
 * abstraction - say IronicNode - can create and cache the actual ngResource instances for N>1
 * configured ironic endpoints.
 *
 * In order to maintain flexibility, it does not build resources, it only accepts preconstructed
 * instances. It is strongly recommended that you store your resource instances using the root
 * URI of the API to which your instance is talking, so that 'https://somecloud.com:6385/v1/nodes'
 * will be stored separately from 'https://someothercloud.com:6385/v1/nodes'.
 */
angular.module('openstack').factory('$$resourceCache',
  function() {
    'use strict';

    /**
     * Cache of all the resource instances.
     *
     * @type {{}}
     */
    var resourceCache = {};

    return {

      /**
       * Store a resource instance in the cache.
       *
       * @param {String} uri The root uri of the resource.
       * @param {*} resource The resource to store.
       * @return {*} The stored resource.
       */
      'set': function(uri, resource) {
        resourceCache[uri] = resource;
        return resource;
      },

      /**
       * Retrieve a resource instance from the cache.
       *
       * @param {String} uri The uri of the resource to retrieve.
       * @return {*|undefined} The resource, or undefined.
       */
      'get': function(uri) {
        if (resourceCache[uri]) {
          return resourceCache[uri];
        }
      },

      /**
       * Check whether or not a resource is in the cache.
       *
       * @param {String} uri The uri of the resource to check.
       * @return {true|false} Whether this resource has already been cached.
       */
      'has': function(uri) {
        return resourceCache.hasOwnProperty(uri);
      },

      /**
       * Remove a specific resource from the cache.
       *
       * @param {String} uri The uri of the resource to remove.
       * @returns {void}
       */
      'remove': function(uri) {
        if (resourceCache.hasOwnProperty(uri)) {
          delete resourceCache[uri];
        }
      },

      /**
       * Return all the uri keys currently registered.
       *
       * @returns {Array} An array of all registered uri keys.
       */
      'keys': function() {
        var keys = [];
        /*eslint-disable guard-for-in*/
        for (var key in resourceCache) {
          keys.push(key);
        }
        /*eslint-enable guard-for-in*/
        return keys;
      },

      /**
       * Remove everything from the cache.
       *
       * @returns {void}
       */
      'clearAll': function() {
        var keys = this.keys();
        for (var i = 0; i < keys.length; i++) {
          delete resourceCache[keys[i]];
        }
      },

      /**
       * Return the number of resources in the cache.
       *
       * @returns {number} The number of resources cached.
       */
      'length': function() {
        return this.keys().length;
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * The $$selectedConfiguration service provides a way of easily accessing, and modifying,
 * the current active cloud configuration used in an application. It will resolve configurations
 * from the $$configuration provider, which provides multiple configuration loading mechanisms.
 */
angular.module('openstack').service('$$selectedConfiguration',
  function($$persistentStorage, $$configuration) {
    'use strict';

    var storageKey = '$$selectedConfiguration';
    var currentConfiguration, selectingConfiguration;

    return {
      /**
       * Retrieve the currently active configuration. If there is not configuration selected,
       * this resources' $promise will fail.
       *
       * @returns {*} The configuration.
       */
      'get': function() {
        // If we're in the process of selecting one, return that.
        if (selectingConfiguration) {
          return selectingConfiguration;
        }

        // If we have a current one, return that.
        if (currentConfiguration) {
          return currentConfiguration;
        }

        // Return the 'selected' configuration by setting it.
        return this.set($$persistentStorage.get(storageKey));
      },

      /**
       * Set a particular configuration as active. The selection will only be persisted to
       * LocalStorage if the selected configuration is valid and available in the
       * $$configuration list.
       *
       * @param {String} configurationId The configuration to activate.
       * @returns {*} The active configuration.
       */
      'set': function(configurationId) {
        // Normalize input

        if (angular.isObject(configurationId)) {
          configurationId = configurationId.id || null;
        }

        // If we're in a resolved state and the ID's match...
        if (currentConfiguration && currentConfiguration.id === configurationId) {
          return currentConfiguration;
        }

        // If we're in a resolving state and the ID's match...
        if (selectingConfiguration && selectingConfiguration.id === configurationId) {
          return selectingConfiguration;
        }

        // If we've reached this point, we need to resolve a new configuration.
        selectingConfiguration = $$configuration.read(configurationId);
        selectingConfiguration.$promise.then(
          function() {
            // Only set this if the promise resolves sucessfully.
            $$persistentStorage.set(storageKey, configurationId);
            currentConfiguration = selectingConfiguration;
          },
          function() {
            // if setting fails, clear the existing storage key if it matches. This case occurs
            // if the system initializes with a config ID that has been invalidated since the
            // previous run.
            if ($$persistentStorage.get(storageKey) === configurationId) {
              $$persistentStorage.remove(storageKey);
            }
          }
        ).finally(
          function() {
            // clear the selecting configuration
            selectingConfiguration = null;
          });

        return selectingConfiguration;
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * This file provides an implementation of the storage API, backed by cookies.
 * This particular implementation is not intelligent: It will access the
 * cookie for this domain, as configured by the $cookieProvider, and will
 * grant access to all values stored this way.
 */
angular.module('openstack').factory('$$cookieStorage',
  function($cookies) {
    'use strict';

    return {

      /**
       * Is this storage type supported?
       *
       * @returns {boolean} True if it is supported, otherwise false.
       */
      'isSupported': function() {
        return true;
      },

      /**
       * Set a value to the provided key in memory storage. If the
       * value already exists it will be overwritten.
       *
       * @param {String} key The key to store the value at.
       * @param {*} value The value to store.
       * @return {*} The stored value.
       */
      'set': function(key, value) {
        $cookies.put(key, angular.toJson(value));
        return value;
      },

      /**
       * Retrieve a value from this storage provider.
       *
       * @param {String} key The key to retrieve.
       * @return {*|undefined} The value, or undefined if it is not set.
       */
      'get': function(key) {
        var result = angular.fromJson($cookies.get(key));
        if (result) {
          return result;
        }
      },

      /**
       * Remove a specific value from the storage provider.
       *
       * @param {String} key The key to remove.
       * @returns {void}
       */
      'remove': function(key) {
        $cookies.remove(key);
      },

      /**
       * Return all the keys currently registered.
       *
       * @returns {Array} An array of all registered keys.
       */
      'keys': function() {
        var all = $cookies.getAll();
        var keys = [];
        /*eslint-disable guard-for-in*/
        for (var key in all) {
          keys.push(key);
        }
        /*eslint-enable guard-for-in*/
        return keys;
      },

      /**
       * Remove everything from the memory storage mechanism.
       *
       * @returns {void}
       */
      'clearAll': function() {
        var all = $cookies.getAll();
        /*eslint-disable guard-for-in*/
        for (var key in all) {
          $cookies.remove(key);
        }
        /*eslint-enable guard-for-in*/
      },

      /**
       * Return the size of the current memory storage.
       *
       * @returns {number} The number of keys in storage.
       */
      'length': function() {
        return this.keys().length;
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * A $$localStorage service behind a common API. If localStorage is not
 * supported, this will log a warning to the console. If you want a provider
 * that gracefully degrades, use $$persistentStorage.
 */
angular.module('openstack').factory('$$localStorage',
  function($window, $log) {
    'use strict';

    /**
     * Detect whether localStorage is supported, and make sure we can write
     * to it.
     */
    var isSupported = (function() {

      // Does it exist?
      if (!$window.localStorage) {
        return false;
      }

      // Can we write to it?
      var testKey = '__' + Math.round(Math.random() * 1e7);
      try {
        $window.localStorage.setItem(testKey, '');
        $window.localStorage.removeItem(testKey);
        return true;
      } catch (e) {
        return false;
      }
    })();

    return {

      /**
       * Is this storage type supported?
       *
       * @returns {boolean} True if it is supported, otherwise false.
       */
      'isSupported': function() {
        return isSupported;
      },

      /**
       * Set a value of the provided key. If the
       * value already exists it will be overwritten.
       *
       * @param {String} key The key to store the value at.
       * @param {*} value The value to store.
       * @return {*} The stored value.
       */
      'set': function(key, value) {
        if (isSupported) {
          $window.localStorage.setItem(key, angular.toJson(value));
          return value;
        }
        $log.warn('$$localStorage not supported');
      },

      /**
       * Retrieve a value from this storage provider.
       *
       * @param {String} key The key to retrieve.
       * @return {*|undefined} The value, or undefined if it is not set.
       */
      'get': function(key) {
        if (isSupported) {
          var result = angular.fromJson($window.localStorage.getItem(key));
          if (result) {
            return result;
          }
        } else {
          $log.warn('$$localStorage not supported');
        }
      },

      /**
       * Remove a specific value from the storage provider.
       *
       * @param {String} key The key to remove.
       * @returns {void}
       */
      'remove': function(key) {
        if (isSupported) {
          return $window.localStorage.removeItem(key);
        }
        $log.warn('$$localStorage not supported');
      },

      /**
       * Return all the keys currently registered.
       *
       * @returns {Array} An array of all registered keys.
       */
      'keys': function() {
        if (isSupported) {
          var keys = [];
          for (var i = 0; i < $window.localStorage.length; i++) {
            keys.push($window.localStorage.key(i));
          }
          return keys;
        }
        $log.warn('$$localStorage not supported');
        return [];
      },

      /**
       * Remove everything from the memory storage mechanism.
       *
       * @returns {void}
       */
      'clearAll': function() {
        if (isSupported) {
          var keys = this.keys();
          for (var i = 0; i < keys.length; i++) {
            this.remove(keys[i]);
          }
        }
        $log.warn('$$localStorage not supported');
      },

      /**
       * Return the size of the current memory storage.
       *
       * @returns {number} The number of keys in storage.
       */
      'length': function() {
        if (isSupported) {
          return $window.localStorage.length;
        }
        $log.warn('$$localStorage not supported');
        return 0;
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * This provides a memory-based key/value storage mechanism. It's provided as
 * a fallback option for all other storage mechanisms, to prevent unexpected
 * runtime failures.
 */
angular.module('openstack').factory('$$memoryStorage',
  function() {
    'use strict';

    var memoryStorage = {};

    return {

      /**
       * Is this storage type supported?
       *
       * @returns {boolean} True if it is supported, otherwise false.
       */
      'isSupported': function() {
        return true;
      },

      /**
       * Set a value to the provided key in memory storage. If the
       * value already exists it will be overwritten.
       *
       * @param {String} key The key to store the value at.
       * @param {*} value The value to store.
       * @return {*} The stored value.
       */
      'set': function(key, value) {
        memoryStorage[key] = value;

        return value;
      },

      /**
       * Retrieve a value from this storage provider.
       *
       * @param {String} key The key to retrieve.
       * @return {*|undefined} The value, or undefined if it is not set.
       */
      'get': function(key) {
        if (memoryStorage.hasOwnProperty(key)) {
          return memoryStorage[key];
        }
      },

      /**
       * Remove a specific value from the storage provider.
       *
       * @param {String} key The key to remove.
       * @returns {void}
       */
      'remove': function(key) {
        delete memoryStorage[key];
      },

      /**
       * Return all the keys currently registered.
       *
       * @returns {Array} An array of all registered keys.
       */
      'keys': function() {
        var keys = [];
        /*eslint-disable guard-for-in*/
        for (var key in memoryStorage) {
          keys.push(key);
        }
        /*eslint-enable guard-for-in*/
        return keys;
      },

      /**
       * Remove everything from the memory storage mechanism.
       *
       * @returns {void}
       */
      'clearAll': function() {
        var keys = [];
        /*eslint-disable guard-for-in*/
        for (var key in memoryStorage) {
          keys.push(key);
        }
        /*eslint-enable guard-for-in*/

        for (var i = 0; i < keys.length; i++) {
          delete memoryStorage[keys[i]];
        }
      },

      /**
       * Return the size of the current memory storage.
       *
       * @returns {number} The number of keys in storage.
       */
      'length': function() {
        return this.keys().length;
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * A convenience component that automatically selects the most secure, and most
 * persistent, storage mechanism available in the current runtime. This does
 * not include sessionStorage, which must be used independently.
 */
angular.module('openstack').factory('$$persistentStorage',
  function($log, $$cookieStorage, $$memoryStorage, $$localStorage) {
    'use strict';

    // Check for local storage.
    if ($$localStorage.isSupported()) {
      return $$localStorage;
    }

    // Check for cookie storage.
    if ($$cookieStorage.isSupported()) {
      return $$cookieStorage;
    }

    $log.warn('Warning: No persistent storage mechanism supported, all storage will be transient.');
    return $$memoryStorage;
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
 *
 * 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.
 */

/**
 * A $$sessionStorage service behind a common API. If sessionStorage is not
 * supported, this will log a warning to the console. If you want a provider
 * that gracefully degrades, use $$persistentStorage.
 */
angular.module('openstack').factory('$$sessionStorage',
  function($window, $log) {
    'use strict';

    /**
     * Detect whether sessionStorage is supported, and make sure we can
     * write to it.
     */
    var isSupported = (function() {

      // Does it exist?
      if (!$window.sessionStorage) {
        return false;
      }

      // Can we write to it?
      var testKey = '__' + Math.round(Math.random() * 1e7);
      try {
        $window.sessionStorage.setItem(testKey, '');
        $window.sessionStorage.removeItem(testKey);
        return true;
      } catch (e) {
        return false;
      }
    })();

    return {

      /**
       * Is this storage type supported?
       *
       * @returns {boolean} True if it is supported, otherwise false.
       */
      'isSupported': function() {
        return isSupported;
      },

      /**
       * Set a value of the provided key. If the
       * value already exists it will be overwritten.
       *
       * @param {String} key The key to store the value at.
       * @param {*} value The value to store.
       * @return {*} The stored value.
       */
      'set': function(key, value) {
        if (isSupported) {
          $window.sessionStorage.setItem(key, angular.toJson(value));
          return value;
        }
        $log.warn('$$sessionStorage not supported');
      },

      /**
       * Retrieve a value from this storage provider.
       *
       * @param {String} key The key to retrieve.
       * @return {*|undefined} The value, or undefined if it is not set.
       */
      'get': function(key) {
        if (isSupported) {
          var result = angular.fromJson($window.sessionStorage.getItem(key));
          if (result) {
            return result;
          }
        } else {
          $log.warn('$$sessionStorage not supported');
        }
      },

      /**
       * Remove a specific value from the storage provider.
       *
       * @param {String} key The key to remove.
       * @returns {void}
       */
      'remove': function(key) {
        if (isSupported) {
          return $window.sessionStorage.removeItem(key);
        }
        $log.warn('$$sessionStorage not supported');
      },

      /**
       * Return all the keys currently registered.
       *
       * @returns {Array} An array of all registered keys.
       */
      'keys': function() {
        if (isSupported) {
          var keys = [];
          for (var i = 0; i < $window.sessionStorage.length; i++) {
            keys.push($window.sessionStorage.key(i));
          }
          return keys;
        }
        $log.warn('$$sessionStorage not supported');
        return [];
      },

      /**
       * Remove everything from the memory storage mechanism.
       *
       * @returns {void}
       */
      'clearAll': function() {
        if (isSupported) {
          var keys = this.keys();
          for (var i = 0; i < keys.length; i++) {
            this.remove(keys[i]);
          }
          return;
        }
        $log.warn('$$sessionStorage not supported');
      },

      /**
       * Return the size of the current memory storage.
       *
       * @returns {number} The number of keys in storage.
       */
      'length': function() {
        if (isSupported) {
          return $window.sessionStorage.length;
        }
        $log.warn('$$sessionStorage not supported');
        return 0;
      }
    };
  });
