// The following code contains unit tests for Fuzzy Logicclasses and functions.

// Test 1 creates a left shoulder fuzzy set and tests the degree of membership 
// function.
function test1() {
    let fs = new FuzzySet_LeftShoulder(100, 50, 50);
    let actual = '' + fs.calculateDOM(75) + ' '
        + fs.calculateDOM(100) + ' '
        + fs.calculateDOM(125);
    let expected = '1 1 0.5';

    compareStringsAndReport('Test 1', actual, expected);
}

// Test 2 creates a right shoulder fuzzy set and tests the degree of membership
// function.
function test2() {
    let fs = new FuzzySet_RightShoulder(100, 50, 50);
    let actual = '' + fs.calculateDOM(75) + ' '
        + fs.calculateDOM(100) + ' '
        + fs.calculateDOM(125);
    let expected = '0.5 1 1';

    compareStringsAndReport('Test 2', actual, expected);
}

// Test 3 creates a singleton fuzzy set and tests the degree of membership
// function.
function test3() {
    let fs = new FuzzySet_Singleton(100, 50, 50);
    let actual = '' + fs.calculateDOM(49) + ' '
        + fs.calculateDOM(51) + ' '
        + fs.calculateDOM(149);
    let expected = '0 1 1';

    compareStringsAndReport('Test 3', actual, expected);
}

// Test 4 creates a triangular fuzzy set and tests the degree of membership
// function.
function test4() {
    let fs = new FuzzySet_Triangle(100, 50, 50);
    let actual = '' + fs.calculateDOM(75) + ' '
        + fs.calculateDOM(100) + ' '
        + fs.calculateDOM(125);
    let expected = '0.5 1 0.5';

    compareStringsAndReport('Test 4', actual, expected);
}

// Test 5 creates a fuzzy variable with three sets and tests the fuzzification
// and defuzzification methods.
function test5() {
    let fv = new FuzzyVariable()
        .addLeftShoulderSet('left', 0, 50, 100)
        .addTriangularSet('center', 50, 100, 150)
        .addRightShoulderSet('right', 100, 150, 200)
        .fuzzify(87.5);

    fv.ORwithDOM(0.5);

    let actual = '' + fv.sets['left'].DOM + ' '
        + fv.sets['center'].DOM + ' '
        + fv.sets['right'].DOM + ' '
        + fv.deFuzzifyMaxAv() + ' '
        + fv.deFuzzifyCentroid(15).toFixed(1);
    let expected = '0.5 0.75 0.5 100 105.0';

    compareStringsAndReport('Test 5', actual, expected);
}

// Test 6 creates a fuzzy AND operation and tests it on two triangular fuzzy sets.
function test6() {
    let actual = new FuzzyAND([
        new FuzzySet_Triangle(100, 50, 50).fuzzify(87.5),
        new FuzzySet_Triangle(200, 50, 50).fuzzify(225)
    ]).getDOM();
    let expected = 0.5;

    compareAndReport('Test 6', actual, expected);
}

// Test 7 creates a fuzzy OR operation and tests it on two triangular fuzzy sets.
function test7() {
    let actual = new FuzzyOR([
        new FuzzySet_Triangle(100, 50, 50).fuzzify(87.5),
        new FuzzySet_Triangle(200, 50, 50).fuzzify(225)
    ]).getDOM();
    let expected = 0.75;

    compareAndReport('Test 7', actual, expected);
}

// Test 8 creates and tests a fuzzy rule.
function test8() {
    let rule = new FuzzyRule(
        new FuzzyAND([
            new FuzzySet_Triangle(100, 50, 50).fuzzify(87.5),
            new FuzzySet_Triangle(200, 50, 50).fuzzify(225)
        ]),
        new FuzzySet_Triangle(300, 50, 50)
    );
    let actual = rule.calculate().getDOM();
    let expected = 0.5;

    compareAndReport('Test 8', actual, expected);
}

// In test 9, fuzzy logic is employed to evaluate the desirability of a weapon.
// Each weapon has its own fuzzy module instance because the desirability 
// inference is based on a unique set of rules specific to that weapon.
function test9() {
    let m = new FuzzyModule();

    let distToTarget = m.createVariable("distToTarget")
        .addLeftShoulderSet("close", 0, 25, 150)
        .addTriangularSet("medium", 25, 150, 300)
        .addRightShoulderSet("far", 150, 300, 1000);

    let desirability = m.createVariable("desirability")
        .addRightShoulderSet("verydesirable", 50, 75, 100)
        .addTriangularSet("desirable", 25, 50, 75)
        .addLeftShoulderSet("undesirable", 0, 25, 50);

    let ammoStatus = m.createVariable("ammoStatus")
        .addRightShoulderSet("loads", 30, 60, 100)
        .addTriangularSet("okay", 0, 30, 60)
        .addTriangularSet("low", 0, 0, 30);

    // close
    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["close"],
    m.variables["ammoStatus"].sets["loads"]]),
        m.variables["desirability"].sets["verydesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["close"],
    m.variables["ammoStatus"].sets["okay"]]),
        m.variables["desirability"].sets["verydesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["close"],
    m.variables["ammoStatus"].sets["low"]]),
        m.variables["desirability"].sets["verydesirable"]);

    // medium
    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["medium"],
    m.variables["ammoStatus"].sets["loads"]]),
        m.variables["desirability"].sets["verydesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["medium"],
    m.variables["ammoStatus"].sets["okay"]]),
        m.variables["desirability"].sets["desirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["medium"],
    m.variables["ammoStatus"].sets["low"]]),
        m.variables["desirability"].sets["undesirable"]);

    // far
    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["far"],
    m.variables["ammoStatus"].sets["loads"]]),
        m.variables["desirability"].sets["desirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["far"],
    m.variables["ammoStatus"].sets["okay"]]),
        m.variables["desirability"].sets["undesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["far"],
    m.variables["ammoStatus"].sets["low"]]),
        m.variables["desirability"].sets["undesirable"]);

    // Fuzzify distance and amount of ammo.
    m.fuzzify("distToTarget", 150);
    m.fuzzify("ammoStatus", 50);

    // Calculate and defuzzify the result.
    let actual = m.deFuzzify("desirability",
        FuzzyModule.DefuzzifyMethod.max_av).toFixed(2);
    let expected = 75;

    compareAndReport('Test 9', actual, expected);
}

// In test 10, fuzzy logic is employed to evaluate the desirability of a weapon.
// Each weapon has its own fuzzy module instance because the desirability 
// inference is based on a unique set of rules specific to that weapon.
function test10() {
    let m = new FuzzyModule();

    let distToTarget = m.createVariable("distToTarget")
        .addLeftShoulderSet("close", 0, 25, 150)
        .addTriangularSet("medium", 25, 150, 300)
        .addRightShoulderSet("far", 150, 300, 1000);

    let desirability = m.createVariable("desirability")
        .addRightShoulderSet("verydesirable", 50, 75, 100)
        .addTriangularSet("desirable", 25, 50, 75)
        .addLeftShoulderSet("undesirable", 0, 25, 50);

    let ammoStatus = m.createVariable("ammoStatus")
        .addRightShoulderSet("loads", 10, 30, 100)
        .addTriangularSet("okay", 0, 10, 30)
        .addTriangularSet("low", 0, 0, 10);

    // close
    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["close"],
    m.variables["ammoStatus"].sets["loads"]]),
        m.variables["desirability"].sets["undesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["close"],
    m.variables["ammoStatus"].sets["okay"]]),
        m.variables["desirability"].sets["undesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["close"],
    m.variables["ammoStatus"].sets["low"]]),
        m.variables["desirability"].sets["undesirable"]);

    // medium
    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["medium"],
    m.variables["ammoStatus"].sets["loads"]]),
        m.variables["desirability"].sets["verydesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["medium"],
    m.variables["ammoStatus"].sets["okay"]]),
        m.variables["desirability"].sets["verydesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["medium"],
    m.variables["ammoStatus"].sets["low"]]),
        m.variables["desirability"].sets["desirable"]);

    // far
    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["far"],
    m.variables["ammoStatus"].sets["loads"]]),
        m.variables["desirability"].sets["desirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["far"],
    m.variables["ammoStatus"].sets["okay"]]),
        m.variables["desirability"].sets["undesirable"]);

    m.addRule(new FuzzyAND([m.variables["distToTarget"].sets["far"],
    m.variables["ammoStatus"].sets["low"]]),
        m.variables["desirability"].sets["undesirable"]);

    // Fuzzify distance and amount of ammo.
    m.fuzzify("distToTarget", 200);
    m.fuzzify("ammoStatus", 8);

    // Calculate and defuzzify the result.
    let actual = m.deFuzzify("desirability",
        FuzzyModule.DefuzzifyMethod.max_av).toFixed(2);
    let expected = 60.42;

    compareAndReport('Test 10', actual, expected);
}