fcf.module({
  name: "fcf:NTest/uniTest.js",
  dependencies: ["fcf:NTest/Test.js"],
  module: function(Test) {
  	fcf.prepareObject(fcf, "uniTest.NDetails");
  	var uniTest = fcf.uniTest;


  	uniTest.NDetails.tests = {};
    uniTest.NDetails.currentTests = undefined;

  	uniTest.add = function(a_groupName, a_testName, a_testFunc) {
  	  fcf.prepareObject(uniTest.NDetails.tests, a_groupName);
  	  uniTest.NDetails.tests[a_groupName][a_testName] = new Test(a_groupName, a_testName, a_testFunc);
  	}


  	uniTest.ETestStates = {
  	  OK: 0,
  	  WARNING: 1,
  	  ERROR: 2,
      WAIT:-1,
  	};

  	uniTest.testStateToString = function(a_state){
  	  return a_state == uniTest.ETestStates.OK      ? "OK" :
  	         a_state == uniTest.ETestStates.WARNING ? "WARNING" :
             a_state == uniTest.ETestStates.WAIT    ? ".." :
  	                                                  "ERROR";
  	}


    uniTest.run = function(a_arrgroups, a_arrtests, a_cb){
      var state = {
        errors: 0,
        warnings: 0,
        completed: 0,
      };

      function completeTests(){
        fcf.log.log("TEST", (state.errors + state.warnings + state.completed) + " eight tests have been completed");
        fcf.log.log("TEST", "Errors: " + state.errors + ";  Warrnings: " + state.warnings +  ";  Successfully:" + state.completed);
        if (a_cb)
          a_cb(state);
      }

      fcf.actions()
      .catch((a_error)=>{
        completeTests();
      })
      .each(uniTest.NDetails.tests, function(a_groupName, a_tests, a_res, a_groupAct) {
        var skeep = fcf.empty(a_arrgroups) ? false : fcf.find(a_arrgroups, a_groupName) === undefined;
        if (skeep){
          a_groupAct.complete();
          return;
        }

        fcf.actions()
        .catch((a_error)=>{
          a_groupAct.error(a_error);
        })
        .each(a_tests, function(a_testName, a_test, a_res, a_testact){
          var skeep = fcf.empty(a_arrtests) ? false : fcf.find(a_arrtests, a_testName) === undefined;
          if (skeep){
            a_testact.complete();
            return;
          }

          var output = "";
          output += "[" + uniTest.testStateToString(a_test.state) + "] ";
          output = fcf.padEnd(output, 8, " ");
          output += "Test ";
          output += "[" + a_groupName + "]";
          output += "[" + a_testName + "]";
          output += " is running ...";
          fcf.log.log("Test", output);

          uniTest.NDetails.currentTests = a_test;

          a_test.run(function(){
            fcf.log.log("Test", a_test.output);
            switch(a_test.state){
              case uniTest.ETestStates.OK:
                ++state.completed;
                a_testact.complete();
                break;
              case uniTest.ETestStates.WARNING:
                ++state.warnings;
                a_testact.complete();
                break;
              case uniTest.ETestStates.ERROR:
                ++state.errors;
                a_testact.error(a_test.error);
                break;
            }
          });

        })

        .then(function(a_res, a_testAct){
          a_groupAct.complete();
          a_testAct.complete()
        });
      })
      .then(function(a_res, a_groupAct){
        completeTests();
        a_groupAct.complete();
      });
    }


    uniTest.equal = function(a_left, a_right) {
      try {
        if (typeof a_left === "object" && typeof a_right === "object")
          uniTest.equalObject(a_left, a_right);
    	  else if (a_left != a_right)
    	    throw new fcf.Exception("ERROR_TEST_FAILED_EQUAL", [a_left, a_right]);
      } catch(e) {
        if (uniTest.NDetails.currentTests)
          uniTest.NDetails.currentTests.setError(e);
      }

  	}

    uniTest.notEqual = function(a_left, a_right) {
      try {
    	  if (a_left == a_right)
    	    throw new fcf.Exception("ERROR_TEST_FAILED_NOT_EQUAL", [a_left, a_right]);
      } catch(e) {
        if (uniTest.NDetails.currentTests)
          uniTest.NDetails.currentTests.setError(e);
      }
  	}


    uniTest.equalObject = function(a_left, a_right, a_path) {
      try {
        uniTest._equalObject(a_left, a_right, a_path);
        uniTest._equalObject(a_right, a_left, a_path);
      } catch(e) {
        if (uniTest.NDetails.currentTests)
          uniTest.NDetails.currentTests.setError(e);
      }
    }

    uniTest._equalObject = function(a_left, a_right, a_path) {
      if (typeof a_path === "undefined"){
        a_path = "";
      }

      if (Array.isArray(a_left)) {
        if (!Array.isArray(a_right))
          throw new fcf.Exception("ERROR_TEST_FAILED_EQUAL_OBJECT_ARG2_NOT_ARRAY", [a_path]);
        if (a_left.length != a_right.length)
          throw new fcf.Exception("ERROR_TEST_FAILED_EQUAL_OBJECT_LENGTH_ARRAY", [a_left.length, a_right.length, a_path]);
        for (var i = 0; i < a_left.length; ++i) {
          uniTest.equalObject(a_left[i], a_right[i], a_path+"[" + i + "]");
        }
      } else if (typeof a_left === "object"){
        if (typeof a_right !== "object")
          throw new fcf.Exception("ERROR_TEST_FAILED_EQUAL_OBJECT_ARG2_NOT_OBJECT", [a_path]);
        for (var key in a_left) {
          if (a_left[key] !== undefined && !(key in a_right))
            throw new fcf.Exception("ERROR_TEST_FAILED_EQUAL_OBJECT_ITEM_NOT_FOUND", {element: key, object: a_path});
          uniTest.equalObject(a_left[key], a_right[key], a_path+"['" + key + "']");
        }
      } else {
        if (a_left != a_right) {
          throw new fcf.Exception("ERROR_TEST_FAILED_EQUAL_OBJECT", [a_left, a_right, a_path]);
        }
      }
    }

  	return uniTest;
  }
});
