/*
 * 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 module of resources that talk with the ironic API.
 */
angular.module('ironic.api', ['openstack', 'ngResource'])
  .config(function($$configurationProvider, $windowProvider) {
    'use strict';

    // Build a default ironic location from the $window provider
    var location = $windowProvider.$get().location;
    var apiRoot = location.protocol + '//' + location.hostname + ':6385';

    // This line registers ironic's default API root, as detected via the current hostname, with
    // the $$configurationProvider's default API detection mechanism.
    $$configurationProvider.$registerDefault('ironic', apiRoot);
  });

/*
 * Copyright (c) 2015 Hewlett Packard Enterprise 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.
 */

/**
 * Logic that needs to be applied to every request against the Ironic API. Includes result
 * transformation and other things.
 */
angular.module('ironic.api').service('ironicApiInterceptor',
  function($q, $http) {
    'use strict';

    /**
     * Ironic's error message is a doubly-encoded JSON string. This checks the body for that
     * field and runs a second decoder.
     *
     * @param {*} responseBody The decoded JSON response body.
     * @param {Function} headers Headers getter.
     * @param {int} status The HTTP Status of the response.
     * @returns {*} The decoded response body.
     */
    function parseErrorResponse (responseBody, headers, status) {
      if (400 <= status && responseBody.hasOwnProperty('error_message')) {
        responseBody.error_message = angular.fromJson(responseBody.error_message);
      }
      return responseBody;
    }

    /**
     * In some cases (as with list responses), we need to return the content of a child property
     * of the returned body rather than the whole response. This method returns a transformation
     * function that applies this change.
     *
     * @param {String} propertyName The property name to reduce to.
     * @returns {Function} A function that reduces the response appropriately.
     */
    function parseChildProperty (propertyName) {
      /**
       * Response transformer for a specific ironic list response. If the property is not found
       * (As with error responses), simply returns the original object.
       *
       * @param {*} responseBody The body received from the server.
       * @returns {Array} List of results.
       */
      return function(responseBody) {
        if (responseBody.hasOwnProperty(propertyName)) {
          return responseBody[propertyName];
        }
        return responseBody;
      };
    }

    return {
      /**
       * Transform the result of an object query from the Ironic API, accommodating for error
       * and list responses.
       *
       * @param {String} childPropertyName (optional) The name of the child property to reduce to.
       * @returns {Array} An array of transformations.
       */
      'response': function(childPropertyName) {
        var transformers = $http.defaults.transformResponse.concat([]);
        transformers.push(parseErrorResponse);

        if (childPropertyName) {
          transformers.push(parseChildProperty(childPropertyName));
        }

        return transformers;
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett Packard Enterprise 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 current supported version of the ironic api. This version is hardcoded until a more
 * sophisticated version negotiation algorithm can be provided. The main blocker is that the
 * current APi abstraction library - ngResource - does not provide pre-flight modificiation of
 * requests via promise resolution. A patch has been provided
 * https://github.com/angular/angular.js/pull/13273; if accepted, this parameter will be
 * incorproated into the ironic_api_interceptor. If not, alternative libraries will have to be
 * evaluated.
 */
angular.module('ironic.api').constant('ironicApiVersion', function() {
  'use strict';
  return '1.14';
});

/*
 * 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.
 */

/**
 * IronicChassis is an ngResource abstraction, which switches which Ironic it talks to based on the
 * selected cloud configuration provided by the $$configuration service. It may be used as if it
 * was an ngResource instance, and supports the methods `query`, `create`, `read`, `update`, and
 * `remove`.
 */
angular.module('ironic.api').factory('IronicChassis',
  function($log, $$selectedConfiguration, $$resourceCache, $resource, $$dummyResource,
           ironicApiInterceptor, ironicApiVersion) {
    'use strict';

    /**
     * This method extracts the current active API root URI from $$configuration, ensures that
     * the appropriate Ironic resources exists in the $$resourceCache, and returns it.
     *
     * @returns {{}} The created, and cached, IronicPort resource.
     */
    function getResource () {
      // Pull the current configuration.
      var currentConfig = $$selectedConfiguration.get();

      // This should resolve the API root, except in cases where the selected configuration is
      // invalid and/or has not yet been resolved.
      var ironicConfig = currentConfig.ironic || {};
      var ironicApiRoot = ironicConfig.apiRoot || null;

      if (!ironicApiRoot) {
        $log.warn('Ironic API Root for Config [' + currentConfig.id + '] not found.');
        return $$dummyResource;
      }

      // Build and/or retrieve a cached instance of the requested service.
      var chassisUrl = ironicApiRoot + '/chassis/:uuid';

      if (!$$resourceCache.has(chassisUrl)) {
        $log.debug("Creating new IronicChassis at: " + chassisUrl);

        var resource = $resource(chassisUrl, {'uuid': '@uuid'}, {
          'query': {
            'method': 'GET',
            'isArray': true,
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response('chassis')
          },
          'create': {
            'method': 'POST',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'read': {
            'method': 'GET',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'update': {
            'method': 'PUT',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'remove': {
            'method': 'DELETE',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          }
        });

        $$resourceCache.set(chassisUrl, resource);
      }

      return $$resourceCache.get(chassisUrl);
    }

    return {
      'query': function() {
        var r = getResource();
        return r.query.apply(r, arguments);
      },
      'create': function() {
        var r = getResource();
        return r.create.apply(r, arguments);
      },
      'read': function() {
        var r = getResource();
        return r.read.apply(r, arguments);
      },
      'update': function() {
        var r = getResource();
        return r.update.apply(r, arguments);
      },
      'remove': function() {
        var r = getResource();
        return r.remove.apply(r, arguments);
      }
    };
  });


/*
 * 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.
 */

/**
 * IronicDriver is an ngResource abstraction, which switches which Ironic it talks to based on the
 * selected cloud configuration provided by the $$configuration service. It may be used as if it
 * was an ngResource instance, and supports the methods `query`, `create`, `read`, `update`, and
 * `remove`.
 */
angular.module('ironic.api').factory('IronicDriver',
  function($log, $$selectedConfiguration, $$resourceCache, $resource, $$dummyResource,
           ironicApiInterceptor, ironicApiVersion) {
    'use strict';

    /**
     * This method extracts the current active API root URI from $$configuration, ensures that
     * the appropriate Ironic resources exists in the $$resourceCache, and returns it.
     *
     * @returns {{}} The created, and cached, IronicPort resource.
     */
    function getResource () {
      // Pull the current configuration.
      var currentConfig = $$selectedConfiguration.get();

      // This should resolve the API root, except in cases where the selected configuration is
      // invalid and/or has not yet been resolved.
      var ironicConfig = currentConfig.ironic || {};
      var ironicApiRoot = ironicConfig.apiRoot || null;

      if (!ironicApiRoot) {
        $log.warn('Ironic API Root for Config [' + currentConfig.id + '] not found.');
        return $$dummyResource;
      }

      // Build and/or retrieve a cached instance of the requested service.
      var driverUrl = ironicApiRoot + '/drivers/:uuid';

      if (!$$resourceCache.has(driverUrl)) {
        $log.debug("Creating new IronicDriver at: " + driverUrl);
        var resource = $resource(driverUrl, {'uuid': '@uuid'}, {
          'query': {
            'method': 'GET',
            'isArray': true,
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response('drivers')
          },
          'create': {
            'method': 'POST',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'read': {
            'method': 'GET',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'update': {
            'method': 'PUT',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'remove': {
            'method': 'DELETE',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          }
        });

        $$resourceCache.set(driverUrl, resource);
      }

      return $$resourceCache.get(driverUrl);
    }

    return {
      'query': function() {
        var r = getResource();
        return r.query.apply(r, arguments);
      },
      'create': function() {
        var r = getResource();
        return r.create.apply(r, arguments);
      },
      'read': function() {
        var r = getResource();
        return r.read.apply(r, arguments);
      },
      'update': function() {
        var r = getResource();
        return r.update.apply(r, arguments);
      },
      'remove': function() {
        var r = getResource();
        return r.remove.apply(r, arguments);
      }
    };
  });

/*
 * Copyright (c) 2015 Hewlett-Packard Enterprise 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.
 */

/**
 * IronicDriverProperites is an ngResource abstraction, which switches which Ironic it talks to
 * based on the selected cloud configuration provided by the $$configuration service. It may be
 * used as if it was an ngResource instance, and supports the methods `query`, `create`, `read`,
 * `update`, and * `remove`.
 */
angular.module('ironic.api').factory('IronicDriverProperties',
  function($log, $$selectedConfiguration, $$resourceCache, $resource, $$dummyResource,
           ironicApiInterceptor, ironicApiVersion) {
    'use strict';

    /**
     * This method extracts the current active API root URI from $$configuration, ensures that
     * the appropriate Ironic resources exists in the $$resourceCache, and returns it.
     *
     * @returns {{}} The created, and cached, IronicPort resource.
     */
    function getResource () {
      // Pull the current configuration.
      var currentConfig = $$selectedConfiguration.get();

      // This should resolve the API root, except in cases where the selected configuration is
      // invalid and/or has not yet been resolved.
      var ironicConfig = currentConfig.ironic || {};
      var ironicApiRoot = ironicConfig.apiRoot || null;

      if (!ironicApiRoot) {
        $log.warn('Ironic API Root for Config [' + currentConfig.id + '] not found.');
        return $$dummyResource;
      }

      // Build and/or retrieve a cached instance of the requested service.
      var driverUrl = ironicApiRoot + '/drivers/properties?driver_name=:driver_name';

      if (!$$resourceCache.has(driverUrl)) {
        $log.debug("Creating new IronicDriverProperties at: " + driverUrl);
        var resource = $resource(driverUrl, {'driver_name': 'driver_name'}, {
          'read': {
            'method': 'GET',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          }
        });

        $$resourceCache.set(driverUrl, resource);
      }

      return $$resourceCache.get(driverUrl);
    }

    return {
      'read': function() {
        var r = getResource();
        return r.read.apply(r, arguments);
      }
    };
  });

/*
 * 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.
 */

/**
 * IronicNode is an ngResource abstraction, which switches which Ironic it talks to based on the
 * selected cloud configuration provided by the $$configuration service. It may be used as if it
 * was an ngResource instance, and supports the methods `query`, `create`, `read`, `update`, and
 * `remove`.
 */
angular.module('ironic.api').factory('IronicNode',
  function($log, $$selectedConfiguration, $$resourceCache, $resource, $$dummyResource,
           ironicApiInterceptor, ironicApiVersion) {
    'use strict';

    /**
     * This method extracts the current active API root URI from $$configuration, ensures that
     * the appropriate Ironic resources exists in the $$resourceCache, and returns it.
     *
     * @returns {{}} The created, and cached, IronicPort resource.
     */
    function getResource () {
      // Pull the current configuration.
      var currentConfig = $$selectedConfiguration.get();

      // This should resolve the API root, except in cases where the selected configuration is
      // invalid and/or has not yet been resolved.
      var ironicConfig = currentConfig.ironic || {};
      var ironicApiRoot = ironicConfig.apiRoot || null;

      if (!ironicApiRoot) {
        $log.warn('Ironic API Root for Config [' + currentConfig.id + '] not found.');
        return $$dummyResource;
      }

      // Build and/or retrieve a cached instance of the requested service.
      var nodeUrl = ironicApiRoot + '/nodes/:uuid';

      if (!$$resourceCache.has(nodeUrl)) {
        $log.debug("Creating new IronicNode at: " + nodeUrl);
        var resource = $resource(nodeUrl, {'uuid': '@uuid'}, {
          'query': {
            'method': 'GET',
            'isArray': true,
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response('nodes')
          },
          'create': {
            'method': 'POST',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'read': {
            'method': 'GET',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'update': {
            'method': 'PUT',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'remove': {
            'method': 'DELETE',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          }
        });

        $$resourceCache.set(nodeUrl, resource);
      }

      return $$resourceCache.get(nodeUrl);
    }

    return {
      'query': function() {
        var r = getResource();
        return r.query.apply(r, arguments);
      },
      'create': function() {
        var r = getResource();
        return r.create.apply(r, arguments);
      },
      'read': function() {
        var r = getResource();
        return r.read.apply(r, arguments);
      },
      'update': function() {
        var r = getResource();
        return r.update.apply(r, arguments);
      },
      'remove': function() {
        var r = getResource();
        return r.remove.apply(r, arguments);
      }
    };
  });

/*
 * 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.
 */

/**
 * IronicPort is an ngResource abstraction, which switches which Ironic it talks to based on the
 * selected cloud configuration provided by the $$configuration service. It may be used as if it
 * was an ngResource instance, and supports the methods `query`, `create`, `read`, `update`, and
 * `remove`.
 */
angular.module('ironic.api').factory('IronicPort',
  function($log, $$selectedConfiguration, $$resourceCache, $resource, $$dummyResource,
           ironicApiInterceptor, ironicApiVersion) {
    'use strict';

    /**
     * This method extracts the current active API root URI from $$configuration, ensures that
     * the appropriate Ironic resources exists in the $$resourceCache, and returns it.
     *
     * @returns {{}} The created, and cached, IronicPort resource.
     */
    function getResource () {
      // Pull the current configuration.
      var currentConfig = $$selectedConfiguration.get();

      // This should resolve the API root, except in cases where the selected configuration is
      // invalid and/or has not yet been resolved.
      var ironicConfig = currentConfig.ironic || {};
      var ironicApiRoot = ironicConfig.apiRoot || null;

      if (!ironicApiRoot) {
        $log.warn('Ironic API Root for Config [' + currentConfig.id + '] not found.');
        return $$dummyResource;
      }

      // Build and/or retrieve a cached instance of the requested service.
      var portUrl = ironicApiRoot + '/ports/:uuid';

      if (!$$resourceCache.has(portUrl)) {
        $log.debug("Creating new IronicPort at: " + portUrl);
        var resource = $resource(portUrl, {'uuid': '@uuid'}, {
          'query': {
            'method': 'GET',
            'isArray': true,
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response('ports')
          },
          'create': {
            'method': 'POST',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'read': {
            'method': 'GET',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'update': {
            'method': 'PUT',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          },
          'remove': {
            'method': 'DELETE',
            'headers': {
              'X-OpenStack-Ironic-API-Version': ironicApiVersion
            },
            'transformResponse': ironicApiInterceptor.response()
          }
        });

        $$resourceCache.set(portUrl, resource);
      }

      return $$resourceCache.get(portUrl);
    }

    return {
      'query': function() {
        var r = getResource();
        return r.query.apply(r, arguments);
      },
      'create': function() {
        var r = getResource();
        return r.create.apply(r, arguments);
      },
      'read': function() {
        var r = getResource();
        return r.read.apply(r, arguments);
      },
      'update': function() {
        var r = getResource();
        return r.update.apply(r, arguments);
      },
      'remove': function() {
        var r = getResource();
        return r.remove.apply(r, arguments);
      }
    };
  });

/*
 * 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.
 */

/**
 * Form validator that will perform an asynchronous check to see if Ironic is
 * available at the provided url. It will expose a variable on the control to
 * which it is applied, with an array of detected API versions.
 */
/*eslint-disable angular/ng_no_services */
angular.module('ironic.api').directive('ironicApiUrl',
  function ($q, $http) {
/*eslint-enable angular/ng_no_services */
    'use strict';

    return {
      'require': 'ngModel',
      'link': function (scope, elm, attrs, ctrl) {

        ctrl.$ironicVersions = [];

        ctrl.$asyncValidators.ironicApiUrl =
          function (modelValue) {
            var def = $q.defer();

            $http.get(modelValue).then(function (result) {
              var name = result.data.name;

              if (name !== 'OpenStack Ironic API') {
                def.reject();
              }

              var versions = result.data.versions || [];
              for (var i = 0; i < versions.length; i++) {
                versions[i] = versions[i].id;
              }
              ctrl.$ironicVersions = versions;
              def.resolve();
            }, function () {
              def.reject();
            });

            return def.promise;
          };
      }
    };
  });
