/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

var _ = require('lodash');
var planner = require('nscale-planner');
var async = require('async');
var sd = require('nscale-util').sysdef();



module.exports = function(config, _containers) {

  var logger = config.logger;


  /**
   * merge in any properties to target that are excelusively only in source
   */
  var merge = function(target, source) {
    var skeys = _.keys(source);
    _.each(skeys, function(skey) {
      if (!target[skey]) {
        target[skey] = source[skey];
      }
    });
    return target;
  };



  /**
   * Deploy a plan
   * See https://github.com/nearform/nscale-planner for a plan example.
   *
   * origin - origin system definition
   * target - target system definition
   * plan   - execution plan
   * mode   - preview or live
   * out    - output handles
   * completeCb - completion callback
   */
  var deployPlan = function(origin, target, plan, mode, out, completeCb) {
    var c;
    var container;
    var containerDef;
    //var tickSize = 100 / plan.length;
    out.initProgress(plan.length, '--> deploying plan...');
    var currProgress = 0;
    var prnt;

    function tick(command, c) {
      //currProgress += tickSize;
      currProgress += 1;
      //out.(currProgress, 'progress');
      out.progress(command + ' ' + c.id + ' ' + (c.type || ''));
    }

    logger.info('deploying plan');
    async.eachSeries(plan, function(step, cb) {
      c = target.topology.containers[step.id] || origin.topology.containers[step.id];
      container = _.cloneDeep(c);
      prnt = target.topology.containers[step.parent] || origin.topology.containers[step.parent];
      containerDef = _.find(target.containerDefinitions, function(cdef) {
        return cdef.id === container.containerDefinitionId;
      });
      if (!containerDef) {
        containerDef = _.find(origin.containerDefinitions, function(cdef) {
          return cdef.id === container.containerDefinitionId;
        });
      }
      if (container.specific && origin.topology.containers[step.id] && origin.topology.containers[step.id].specific) {
        container.specific = merge(container.specific, origin.topology.containers[step.id].specific);
      }
      logger.info('calling: ' + step.cmd);

      // matchup containers to type and apply matching function remove executor
      var err = 'no matching container available for type';
      if (containerDef && containerDef.type) {
        var impl = _containers[containerDef.type];
        if (!impl) { logger.error(err); return cb(err); }
        tick(step.cmd, container);
        impl[step.cmd](mode, prnt.specific, target, containerDef, container, out, function(err, newTarget, replace){
          if (newTarget) {
            target = newTarget;
            if (replace) {
              _.each(replace, function(repl) {
                plan = sd.replaceId(repl.oldId, repl.newId, plan);
              });
            }
          }
          cb(err);
        });
      }
      else {
        cb();
      }
    }, function(err) {
      completeCb(err, target);
    });
  };



  var deploy = function(user, systemId, revisionId, deployed, target, sr, mode, out, cb) {
    out.stdout('--> deploying...');

    if (target) {
      logger.info('creating deployment plan');
      var plan = planner(deployed, target);
      out.plan(plan);
      if (!plan) {
        out.stdout('Unable to plan! -- THIS IS SERIOUS, please report it', 'warn');
        cb(new Error('Unable to plan! -- THIS IS SERIOUS, please report it'));
      }
      else if (plan.length === 0) {
        out.stdout('Nothing to do -- if it should do something, please report it', 'warn');
        cb();
      }
      else {
        deployPlan(deployed, target, plan, mode, out, function(err, updatedSystem) {
          if (mode !== 'preview') {
            if (updatedSystem.dirty) {
              updatedSystem.dirty = false;
              sr.commitRevision(user, updatedSystem.id, 'identifier change on deploy', updatedSystem, function(err2, revisionId) {
                sr.markDeployedRevision(user, updatedSystem.id, revisionId, function(err3) {
                  cb(err || err2 || err3);
                });
              });
            }
            else {
              sr.markDeployedRevision(user, systemId, revisionId, function(err2) {
                cb(err || err2);
              });
            }
          }
          else {
            cb(err);
          }
        });
      }
    }
    else {
      out.stdout('no target revision - aborting', 'warn');
      cb(new Error('no target revision'));
    }
  };



  var construct = function() {
  };



  construct();
  return {
    deploy: deploy,
    deployPlan: deployPlan
  };
};


@@ -3787,24 +3787,379 @@
 %7D);%0A  %7D;%0A%0A%0A%0A
+  var commitRevision = function(sr, user, system, cb) %7B%0A    sr.writeFile(system.id, system.topology.name + '.json', JSON.stringify(system, null, 2), function(err) %7B%0A      if (err) %7B return cb(err); %7D%0A      sr.commitRevision(user, system.id, 'identifier change on deploy', function(err, revisionId) %7B%0A        cb(err, revisionId);%0A      %7D);%0A    %7D);%0A  %7D;%0A%0A%0A%0A
   var deploy
@@ -4971,27 +4971,24 @@
             
-sr.
 commitRevisi
@@ -4994,61 +4994,16 @@
 ion(
-use
+s
 r, u
-pdatedSystem.id, 'identifier change on deploy'
+ser
 , up

write contents to appropriate target post deploy