/* jslint node: true, esnext: true */

"use strict";

const path = require('path'),
  events = require('events'),
  uti = require('uti'),
  scopeReporter = require('scope-reporter'),
  step = require('kronos-step'),
  service = require('kronos-service');

/*
 * creates a kronos service manager.
 * Options:
 *    name  - name of the manager defaults to 'kronos'
 *    logger - logger to use
 *
 * @param {Object} options
 * @return {Promise} a promise with the service manager as its value
 */
exports.manager = function (options) {
  if (!options) {
    options = {};
  }

  const logger = options.logger || console;
  const name = options.name || "kronos";

  if (!options.scopeReporter) {
    options.scopeReporter = scopeReporter.createReporter(step.ScopeDefinitions, scopeReporter.createLoggingAdapter(
      logger));
  }

  // stores all the available flows
  const flows = {};

  // stores all the available steps
  const steps = {};

  // this is a general service registry.
  const services = {};

  // Create the manager object
  const manager = Object.create(new events.EventEmitter(), {
    toString: {
      value: function () {
        return this.name;
      }
    },
    name: {
      value: name
    },
    flows: {
      value: flows
    },
    steps: {
      value: steps
    },
    services: {
      value: services
    },
    scopeReporter: {
      value: options.scopeReporter
    }
  });

  /**
   * Returns a service object for the given key.
   * If the service does not exists it will return undefined.
   * @param serviceName The name under the service was registered.
   * @param {Function} provideIfMissing callback to deliver the missing module if it is not already present
   * @return The registered service for the given key
   */
  manager.serviceGet = function (serviceName, provideIfMissing) {
    const c = services[serviceName];
    if (c === undefined && provideIfMissing) {
      this.serviceRegister(serviceName, provideIfMissing(serviceName));
      return services[serviceName];
    }
    return c;
  };

  /**
   * Registers an service in the service manager.
   * If there is already registered under this serviceName, it will be overwritten.
   * Services can derive from already registered services values.name then references a former
   * registered service wich is used as a prototype for the new service
   * @param serviceName A name used to identify the service.
   * @param service The service object to be stored.
   * @return {Service} registered service
   */
  manager.serviceRegister = function (serviceOrServiceName, values) {

    if (typeof serviceOrServiceName === 'string') {
      if (values.name) {
        serviceOrServiceName = service.createService(serviceOrServiceName, values, this.serviceGet(values.name));
      } else {
        serviceOrServiceName = service.createService(serviceOrServiceName, values);
      }
    }
    services[serviceOrServiceName.name] = serviceOrServiceName;
    manager.emit('serviceRegistered', serviceOrServiceName);

    return serviceOrServiceName;
  };

  /**
   * Deletes the service registered under the given serviceName
   * @param serviceName The name identifying the service to be deleted
   */
  manager.serviceDelete = function (serviceName) {
    if (services[serviceName]) {
      delete services[serviceName];
    }
    manager.emit('serviceDeleted', serviceName);
  };

  /**
   * registers a step at the service manager. This is a step factory. The name used here
   * will be referenced from the flow. Then this stepFactory will be used to create a step instance
   * from the flow.
   *
   * @param {Step} step The step factory to be registered
   * Events:
   * 	emits 'stepRegistered' event for the newly registered step implementation
   */
  manager.registerStep = function (step) {
    const name = step.name;

    if (steps[name]) {
      if (steps[name] === step) {
        return;
      }
    }

    steps[name] = step;
    manager.emit('stepRegistered', step);
  };

  // @Deprecated use manager.registerStep instead
  manager.registerStepImplementation = manager.registerStep;

  /**
   * Creates a step instance for a given step configuration.
   * The steps type needs to be registered before he can be referenced in
   * the step configuration.
   * @param {Object} configuration
   * @return {Step} ready for use
   * @throws if given step type is not registered
   */
  manager.getStepInstance = function (configuration) {
    const stepImpl = steps[configuration.type];
    if (stepImpl) {
      return stepImpl.createInstance(this, this.scopeReporter, configuration);
    }
    throw new Error(
      `Could not find the step implementation: '${configuration.type}'.\n Available types are: ${Object.keys(steps).join(',')}`
    );
  };

  /**
   * Stops execution and frees all used resources.
   * It will stop each flow.
   * Then all services
   * @return {Promise} that fullfils to the manager
   */
  manager.shutdown = function () {
    return Promise.all(Object.keys(flows).map(name => flows[name].stop())).
    then(Promise.all(Object.keys(services).map(name => services[name].stop()))).
    then(Promise.resolve(manager));
  };

  /**
   * Register a new flow. If it is a new flow it will just be added. If there is
   * an existing flow with this name the flow will be replaced.
   * If the flow is currently running, it will be stopped first
   *
   * Events:
   * 	emits 'flowStateChanged' event for a newly registered flow which has to be replaced
   *        'flowRegistered' event for every newly registered flow
   *
   * @param  {Step} flow The new flow to be registered
   * @return {Promise} fullfilling to the newly created flow
   */
  manager.registerFlow = function (newFlow) {
    if (flows[newFlow.name]) {
      // first delete old flow
      return manager.deleteFlow(flows[newFlow.name].name).then(function () {
        manager.emit('flowStateChanged', newFlow);
        return Promise.resolve(newFlow);
      });

    } else {
      // The flow is new
      flows[newFlow.name] = newFlow;
      manager.emit('flowRegistered', newFlow);
      return Promise.resolve(newFlow);
    }
  };

  /**
   * Deletes a flow from the stored flow defenitions. If the flow
   * is currently running, it will be stopped first. After it
   * could be stopped, it will be deleted.
   * @param {string} flowName the name of the flow
   * @return {Promise} returns a promise that is fullfilled when the flow is removed
   *         or one that rejects if there is no flow for the given flowName
   */
  manager.deleteFlow = function (flowName) {
    const flow = flows[flowName];

    if (flow) {
      delete flows[flowName];
      return flow.stop().then(flow.remove());
    }

    return Promise.reject(new Error(`No such flow ${flowName}`));
  };

  return uti.initialize({
    definitionFileName: path.join(__dirname, '..', 'uti.json')
  }).then((uti) => Promise.resolve(manager));
};

@@ -2240,16 +2240,22 @@
 services
+.%0A   *
  values.
@@ -2283,21 +2283,16 @@
 a former
-%0A   *
  registe
@@ -2304,16 +2304,17 @@
 ervice w
+h
 ich is u
@@ -2316,16 +2316,21 @@
  is used
+%0A   *
  as a pr
@@ -3286,24 +3286,91 @@
 iceName%5D) %7B%0A
+      services%5BserviceName%5D.stop().then(%0A        function () %7B%0A    
       delete
@@ -3393,26 +3393,26 @@
 eName%5D;%0A    
-%7D%0A
+  
     manager.
@@ -3448,16 +3448,41 @@
 eName);%0A
+        %7D%0A      );%0A    %7D%0A
   %7D;%0A%0A  

stop services before deleting them