/* global module */
/* jslint node: true */
/* jslint indent: 2 */
'use strict';

var async = require('async'),
  expect = require('chai').expect,
  child_process = require('child_process');

var myObject = {};

/**
 * Démarre les TU
 * @param {object} options Tous les paramètres permettant de lancer les TU à savoir :
 *   - {string} description : Description des TU
 *   - {string} root : Racine du namespace
 *   - {object} object : Objet à tester
 *   - {object} dataset : Objet qui contient toutes les valeurs qui seront envoyées aux différentes fonctions à tester
 *   - {object} wrapper : Objet qui contient les wrapper à utilisé pour lancer le test sur la fonction
 * @param {function} callback Callback qui devrait être appelée aprés le traitement (optionnel)
 * @return {undefined} Return undefined
 */
myObject.start = function(options) {
  describe(options.description, function() {
    myObject.mapKeys({
      object: options.object,
      namespace: options.root,
      dataset: options.dataset,
      wrapper: options.wrapper
    });
  });
};

/**
 * Parcours les paramètres d'une vairiable à la recherche de propriétés de type Function
 * @param {object} options Tous les paramètres permettant de lancer les TU à savoir :
 *   - {string} namespace : Namespace de la fonction à tester
 *   - {object} object : Objet à parcourir
 *   - {object} dataset : Objet qui contient toutes les valeurs qui seront envoyées aux différentes fonctions à tester
 *   - {object} wrapper : Objet qui contient les wrapper à utilisé pour lancer le test sur la fonction
 * @return {undefined} Return undefined
 */
myObject.mapKeys = function(options) {
  // Si la propriété est un object, on le parcours
  if (typeof options.object === 'object') {
    // Pour chaque clé
    async.eachSeries(Object.keys(options.dataset), function(key, callback) {
      // Si c'est une fonction, on peut donc la tester
      if (typeof options.object[key] === 'function') {
        // Si un jeu de donnée est présent on lance le test
        if (options.dataset[key]) {
          myObject.run({
            data: options.dataset[key],
            fn: options.object[key].bind(options.object),
            namespace: options.namespace + '.' + key,
            wrapper: options.wrapper[key]
          });
        }
      } else {
        // Sinon, on essaye de le mapper à nouveau
        if (typeof options.object[key] === 'object') {
          myObject.mapKeys({
            dataset: options.dataset[key],
            namespace: options.namespace + '.' + key,
            object: options.object[key],
            wrapper: options.wrapper[key]
          });
        }
      }
      return callback();
    });
  }
};

/**
 * Parcours les paramètres d'une vairiable à la recherche de propriétés de type Function
 * @param {object} options Tous les paramètres permettant de lancer les TU à savoir :
 *   - {string} namespace : Namespace de la fonction à tester
 *   - {object} data : Données de test
 *   - {function} fn : Fonction à tester
 *   - {function} wrapper : Wrapper permettant d'appeler correctement la fonction à tester
 * @return {undefined} Return undefined
 */
myObject.run = function(options) {
  if (typeof options.fn === 'function') {
    describe('#' + options.namespace + '()', function() {
      async.eachSeries(options.data, function(item, callback) {
        it(item.label, function(done) {
          // Ajoute le wrapper par défaut si nécessaire
          if (!options.wrapper) options.wrapper = myObject.wrapper;
          // Lancement du test
          options.wrapper(options.fn, item, function(result) {
            myObject.test(result, item.result);
            return done();
          })
        });
        return callback();
      });
    });
  }
};

/**
 * Wrapper par défaut
 * @param {function} fn Fonction à tester
 * @param {object} item Item en cours
 * @param {function} cb Callback appelée à la fin du traitement, avec comme paramètre disponible :
 *  - {} value Valeur à tester
 * @return {undefined} Return undefined
 */
myObject.wrapper = function(fn, item, cb) {
  return cb(fn(item.arguments));
};

/**
 * Permet d'effectuer le test correspondant au résultat souhaité (se base sur les propriétés de la variable résult)
 * @param {} value Valeur à tester, peut importe le type
 * @param {object} result Variable indiquant le résultat souhaité :
 *   - not : Indique que l'on souhaite tester 'not.'
 *   - include : Indique que l'on souhaite tester 'include'
 *   - length : Indique que l'on souhaite tester 'to.have.length'
 *   - property : Indique que l'on souhaite tester 'to.have.property'
 *   - be : Indique que l'on souhaite tester 'to.be.a'
 *   - equal : Indique que l'on souhaite tester de 'to.equal'
 * @return {object} Résultat du test
 */
myObject.test = function(value, result) {
  var res = expect(value);
  // Test que la valeur retournée est inclue dans le résultat
  if (typeof result.include !== 'undefined') return res.include(result.include);
  res = res.to;
  // Test que la valeur retournée n'est pas égale au résultat
  if (typeof result.not !== 'undefined') res = res.not;
  // Test de la valeur
  if (typeof result.equal !== 'undefined') return res.equal(result.equal);
  // Test de la longueur
  if (typeof result.length !== 'undefined') return res.have.length(result.length);
  // Test de la propriété
  if (typeof result.property !== 'undefined') return res.have.property(result.property);
  // Test du type
  if (typeof result.be !== 'undefined') return res.be.a(result.be);
};

/**
 * Permet de tester si le(s) package(s) est(sont) disponible(s) sur la machine
 * @param {object} options Variable indiquant les données suivantes :
 *   - packages : {array} Liste des noms de packages à tester
 *   - description : {string} [Optional] Description du test
 * @return {undefined} Return undefined
 */
myObject.which = function(options) {
  var description = options.description || 'Test de la présence sur la machine du/des package(s) suivant(s) : ' + options.packages.join(', ')
  describe(description, function() {
    async.eachSeries(options.packages, function(item, callback) {
      it(item, function(done) {
        var res = {
            stdout: [],
            stderr: []
          },
          err = null;
        // Spawn du process qui vérifie la présence du paquet
        var child = child_process.spawn('which', [item], {
          cwd: __dirname
        });
        // Write stdout in Logs
        child.stdout.on('data', function(data) {
          var str = data.toString();
          res.stdout.push(str);
        });
        // Write stderr in Logs
        child.stderr.on('data', function(data) {
          var str = data.toString();
          res.stderr.push(str);
        });
        // Write error process in Logs
        child.on('error', function(data) {
          var str = data.toString();
          if (!err) err = [];
          err.push(str);
        });
        // On close of process
        child.on('close', function(code) {
          if (!err) {
            myObject.test((res.stdout.length > 0), {
              'equal': true
            });
          } else {
            console.log({
              'package': item,
              'error': err,
              'res': res,
              'code': code
            });
          }
          return done();
        });
      });
      return callback();
    });
  });
};

module.exports = myObject;