(function () {
  'use strict';

  angular.module('xos.diagnostic')
  .service('Services', function($resource){
    return $resource('/xos/services/:id', {id: '@id'});
  })
  .service('Tenant', function($resource){
    return $resource('/xos/tenants', {id: '@id'}, {
      queryVsgInstances: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: (res) => {

            // NOTE
            // Note that VCPETenant is now VSGTenant.

            let instances = [];

            angular.forEach(res.data, (tenant) => {
              let info = JSON.parse(tenant.service_specific_attribute);
              if(info && info.instance_id){
                instances.push(info.instance_id);
              }
            });

            return instances;
          }
        }
      },
      getSubscriberTag: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: (res) => {
            // NOTE we should receive only one vOLT tenant here
            return JSON.parse(res.data[0].service_specific_attribute);
          }
        }
      }
    });
  })
  .service('Ceilometer', function($http, $q, Instances) {

    /**
    * Get stats for a single instance
    */
    this.getInstanceStats = (instanceUuid) => {
      let deferred = $q.defer();

      $http.get('/xoslib/xos-instance-statistics', {params: {'instance-uuid': instanceUuid}})
      .then((res) => {
        deferred.resolve(res.data);
      })
      .catch((e) => {
        deferred.reject(e);
      })

      return deferred.promise;
    };

    /**
    * Collect stats for an array of instances
    */
    this.getInstancesStats = (instances) => {
      let deferred = $q.defer();
      let instancePromises = [];
      let instanceList = [];

      // retrieve instance details
      instances.forEach((instanceId) => {
        instancePromises.push(Instances.get({id: instanceId}).$promise);
      });

      // get all instance data
      $q.all(instancePromises)
      .then((_instanceList) => {
        instanceList = _instanceList;
        let promises = [];
        // foreach instance query stats
        instanceList.forEach((instance) => {
          promises.push(this.getInstanceStats(instance.instance_uuid));
        });
        return $q.all(promises);
      })
      .then(stats => {
        // augment instance with stats information
        instanceList.map((instance, i) => {
          instance.stats = stats[i];
        });
        deferred.resolve(instanceList);
      })
      .catch(deferred.reject);

      return deferred.promise;
    };

    this.getContainerStats = (containerName) => {
      const deferred = $q.defer();

      let res = {};

      $http.get('/xoslib/meterstatistics', {params: {'resource': containerName}})
      .then((containerStats) => {
        res.stats = containerStats.data;
        return $http.get('/xoslib/meterstatistics', {params: {'resource': `${containerName}-eth0`}})
      })
      .then((portStats) => {
        res.port = {
          eth0: portStats.data
        };
        return $http.get('/xoslib/meterstatistics', {params: {'resource': `${containerName}-eth1`}})
      })
      .then((portStats) => {
        res.port.eth1 = portStats.data;
        deferred.resolve(res);
      })
      .catch((e) => {
        deferred.reject(e);
      })

      return deferred.promise;
    }
  })
  .service('Slice', function($resource){
    return $resource('/xos/slices', {id: '@id'});
  })
  .service('Instances', function($resource){
    return $resource('/xos/instances/:id', {id: '@id'});
  })
  .service('Node', function($resource, $q, Instances){
    return $resource('/xos/nodes', {id: '@id'}, {
      queryWithInstances: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: function(res){

            // TODO update the API to include instances in nodes
            // http://stackoverflow.com/questions/14573102/how-do-i-include-related-model-fields-using-django-rest-framework

            const deferred = $q.defer();

            let requests = [];

            angular.forEach(res.data, (node) => {
              requests.push(Instances.query({node: node.id}).$promise);
            })

            $q.all(requests)
            .then((list) => {
              res.data.map((node, i) => {
                node.instances = list[i];
                return node;
              });
              deferred.resolve(res.data);
            })

            return deferred.promise;
          }
        }
      }
    });
  })
  .service('Subscribers', function($resource, $q, SubscriberDevice){
    return $resource('/xoslib/cordsubscriber/:id', {id: '@id'}, {
      update: {
        method: 'PUT',
        isArray: false
      },
      queryWithDevices: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: function(res){

            /**
            * For each subscriber retrieve devices and append them
            */

            let deferred = $q.defer();

            let requests = [];

            angular.forEach(res.data, (subscriber) => {
              requests.push(SubscriberDevice.query({id: subscriber.id}).$promise);
            })

            $q.all(requests)
            .then((list) => {

              // adding devices

              res.data.map((subscriber, i) => {
                subscriber.devices = list[i];
                subscriber.type = 'subscriber';

                subscriber.devices.map(d => d.type = 'device')

                return subscriber;
              });

              // faking to have 2 subscriber
              // res.data.push(angular.copy(res.data[0]));

              deferred.resolve(res.data);
            })

            return deferred.promise;
          }
        }
      },
      getWithDevices: {
        method: 'GET',
        isArray: false,
        interceptor: {
          response: (res) => {
            let d = $q.defer();

            SubscriberDevice.query({id: res.data.id}).$promise
            .then(devices => {
              devices.map(d => d.type = 'device');
              res.data.devices = devices;
              res.data.type = 'subscriber';
              d.resolve(res.data);
            })
            .catch(err => {
              d.reject(err);
            });

            return d.promise;
          }
        }
      }
    });
  })
  .service('SubscriberDevice', function($resource){
    return $resource('/xoslib/rs/subscriber/:id/users/', {id: '@id'});
  })
  .service('ServiceRelation', function($q, lodash, Services, Tenant, Slice, Instances){

    // count the mas depth of an object
    const depthOf = (obj) => {
      var depth = 0;
      if (obj.children) {
        obj.children.forEach(function (d) {
          var tmpDepth = depthOf(d);
          if (tmpDepth > depth) {
            depth = tmpDepth
          }
        })
      }
      return 1 + depth
    };

    // find all the relation defined for a given root
    const findLevelRelation = (tenants, rootId) => {
      return lodash.filter(tenants, service => {
        return service.subscriber_service === rootId;
      });
    };

    const findSpecificInformation = (tenants, rootId) => {
      var tenants = lodash.filter(tenants, service => {
        return service.provider_service === rootId && service.subscriber_tenant;
      });

      var info;

      tenants.forEach((tenant) => {
        if(tenant.service_specific_attribute){
          info = JSON.parse(tenant.service_specific_attribute);
        }
      });

      return info;
    };

    // find all the service defined by a given array of relations
    const findLevelServices = (relations, services) => {
      const levelServices = [];
      lodash.forEach(relations, (tenant) => {
        var service = lodash.find(services, {id: tenant.provider_service});
        levelServices.push(service);
      });
      return levelServices;
    };

    const buildLevel = (tenants, services, rootService, rootTenant, parentName = null) => {

      // build an array of unlinked services
      // these are the services that should still placed in the tree
      var unlinkedServices = lodash.difference(services, [rootService]);

      // find all relations relative to this rootElement
      const levelRelation = findLevelRelation(tenants, rootService.id);
      // find all items related to rootElement
      const levelServices = findLevelServices(levelRelation, services);

      // remove this item from the list (performance
      unlinkedServices = lodash.difference(unlinkedServices, levelServices);

      rootService.service_specific_attribute = findSpecificInformation(tenants, rootService.id);

      if(rootService.humanReadableName === 'service_vbng'){
        rootService.humanReadableName = 'service_vrouter'
      }

      const tree = {
        name: rootService.humanReadableName,
        parent: parentName,
        type: 'service',
        service: rootService,
        tenant: rootTenant,
        children: []
      };

      lodash.forEach(levelServices, (service) => {
        if(service.humanReadableName === 'service_ONOS_vBNG' || service.humanReadableName === 'service_ONOS_vOLT'){
          // remove ONOSes from service chart
          return;
        }
        let tenant = lodash.find(tenants, {subscriber_tenant: rootTenant.id, provider_service: service.id});
        tree.children.push(buildLevel(tenants, unlinkedServices, service, tenant, rootService.humanReadableName));
      });

      // if it is the last element append internet
      if(tree.children.length === 0){
        tree.children.push({
          name: 'Router',
          type: 'router',
          children: []
        });
      }

      return tree;
    };

    const buildSubscriberServiceTree = (services, tenants, subscriber = {id: 1, name: 'fakeSubs'}) => {

      // find the root service
      // it is the one attached to subsriber_root
      // as now we have only one root so this can work
      const rootTenant = lodash.find(tenants, {subscriber_root: subscriber.id});
      const rootService = lodash.find(services, {id: rootTenant.provider_service});

      const serviceTree = buildLevel(tenants, services, rootService, rootTenant);

      return {
        name: subscriber.name || subscriber.humanReadableName,
        parent: null,
        type: 'subscriber',
        children: [serviceTree]
      };

    };

    // applying domain knowledge to build the global service tree
    const buildServiceTree = (services, tenants) => {

      // TODO refactor
      const buildChild = (services, tenants, currentService) => {

        if(currentService.humanReadableName === 'service_vbng'){
          currentService.humanReadableName = 'service_vrouter'
        }

        const response = {
          type: 'service',
          name: currentService.humanReadableName,
          service: currentService
        };

        let tenant = lodash.find(tenants, {subscriber_service: currentService.id});
        if(tenant){
          let next = lodash.find(services, {id: tenant.provider_service});
          response.children = [buildChild(services, tenants, next)];
        }
        else {
          response.children = [
            {
              name: 'Router',
              type: 'router',
              children: []
            }
          ]
        }
        delete currentService.id; // conflict with d3
        return response;
      }

      let baseService = lodash.find(services, {id: 3});
      
      if(!angular.isDefined(baseService)){
        console.error('Missing Base service!');
        return;
      }

      const baseData = {
        name: 'Subscriber',
        type: 'subscriber',
        parent: null,
        children: [buildChild(services, tenants, baseService)]
      };
      return baseData;
    };

    const getBySubscriber = (subscriber) => {
      var deferred = $q.defer();
      var services, tenants;
      Services.query().$promise
      .then((res) => {
        services = res;
        return Tenant.query().$promise;
      })
      .then((res) => {
        tenants = res;
        deferred.resolve(buildSubscriberServiceTree(services, tenants, subscriber));
      })
      .catch((e) => {
        throw new Error(e);
      });

      return deferred.promise;
    };

    const get = () => {
      var deferred = $q.defer();
      var services, tenants;
      Services.query().$promise
      .then((res) => {
        services = res;
        return Tenant.query({kind: 'coarse'}).$promise;
      })
      .then((res) => {
        tenants = res;
        deferred.resolve(buildServiceTree(services, tenants));
      })
      .catch((e) => {
        throw new Error(e);
      });

      return deferred.promise;
    }

    // export APIs
    return {
      get: get,
      buildServiceTree: buildServiceTree,
      getBySubscriber: getBySubscriber,
      buildLevel: buildLevel,
      buildSubscriberServiceTree: buildSubscriberServiceTree,
      findLevelRelation: findLevelRelation,
      findLevelServices: findLevelServices,
      depthOf: depthOf,
      findSpecificInformation: findSpecificInformation
    }
  });

}());