/*
	Licensed Material - Property of IBM

	(C) Copyright 2015 IBM Corp.

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


'use strict';

/*
This module allows for the execution of a child process, and allows for
arguments to be passed to that process when it is spawned.
 */

// Public modules
var spawn = require('child_process').spawn;
var Q = require('q');
var log = require('npmlog');

// Private modules
var consts = require('./consts');
var errors = require('mdo-errors');

/*
Silly logs a message.

message - Message to log
 */
function logSilly(message) {
  log.silly(consts.MODULE, message);
}

/*
Verbose logs a message.

message - Message to log
 */
function logVerbose(message) {
  log.verbose(consts.MODULE, message);
}

/*
Spawns a child process. The child process will be returned. The command argument
must be initialized.

command - Process to spawn
args - Arguments to pass to the process
 */
function spawnChild(command, args, directory) {
  var child;        // Child process
  var env;          // Process environment

  logVerbose('Spawning child process.');

  // If arguments are uninitialized, initialize them
  if (!args)
    args = [];
    
  if (!directory)
    directory = process.cwd();

  env = process.env;

  // Adjust the command variable for Windows
  if (process.platform === consts.WIN) {
    args.unshift(command);
    args.unshift(consts.WIN_ARG_PREFIX);
    command = consts.WIN_CMD;
  }

  logSilly('Spawn command:' + command);
  logSilly('Spawn arguments: ' + args);
  logSilly('Spawn environment ' + env);

  child = spawn(command, args, {env: env, cwd: directory});

  logSilly('Child process: ' + child);

  return child;
}

/*
Provides callbacks for the spawned child process. Callbacks include standard
error, standard output, error, closing code. An object containing this
information is returned through a promise, that is resolved if no error occurs.
Otherwise, the resultant object is returned through the promise rejection. A
child must be passed.

child - Child for callbacks
 */
function setupCallbacks(child) {
  var def;        // Promise callback
  var execRes;    // Resultant data

  logVerbose('Monitoring child callbacks');

  def = Q.defer();
  execRes = {};
  execRes.stdOut = '';
  execRes.stdErr = '';
  execRes.err = '';

  // Monitor standard error
  child.stderr.on(consts.DATA, function (message) {
    execRes.stdErr = execRes.stdErr + message;
    logSilly('Standard error: ' + message);
  });

  // Monitor standard output
  child.stdout.on(consts.DATA, function (message) {
    execRes.stdOut = execRes.stdOut + message;
    logSilly('Standard output: ' + message);
  });

  // Monitor closing code
  child.on(consts.CLOSE, function (code) {
    execRes.code = code;

    // If no standard output, or error resolve, and reject based on closing code
    if (code === 0)
      def.resolve(execRes);
    else
      def.reject(execRes);

    logSilly('Closing code: ' + code);
  });

  // Monitor errors
  child.on(consts.ERROR, function (err) {
    execRes.err = execRes.err + err;
    logSilly('Error: ' + err);
  });

  return def.promise;
}

/*
Executes a command with the provided arguments. A promise callback is returned
that is resolved if the process was created successfully. Otherwise, the promise
is rejected with an error from mdo-errors.

command - Process to execute
args - Array of arguments for process
logLevel - Log level (silly, or verbose)
 */
function run(command, args, directory) {
  var def;          // Deferred callback
  var child;        // Spawned child
  var promise;      // Resultant promise

  // Set the log level if passed
  if (global.MDO_CLI_LOG_LEVEL)
    log.level = global.MDO_CLI_LOG_LEVEL;

  logVerbose('Running exec command.');
  logSilly('Initial command: ' + command);
  logSilly('Initial arguments: ' + args);

  // Spawn the child process if a command is given. Otherwise, reject
  if (command) {
    child = spawnChild(command, args, directory);
    promise = setupCallbacks(child);
  } else {
    def = Q.defer();
    def.reject(errors.getError(consts.NO_PROCESS));
    promise = def.promise;
  }

  return promise;
}

module.exports.run = run;

