/**
 * Author: dkanus
 * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
 * License: GPL
 * Copyright 2023 Anton Tarasenko
 *------------------------------------------------------------------------------
 * This file is part of Acedia.
 *
 * Acedia is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License, or
 * (at your option) any later version.
 *
 * Acedia is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Acedia.  If not, see <https://www.gnu.org/licenses/>.
 */
class TEST_Voting extends TestCase
    abstract
    dependsOn(VotingModel);

enum ExpectedOutcome {
    TEST_EO_Continue,
    TEST_EO_End,
};

protected static function VotingModel MakeVotingModel(bool drawMeansWin) {
    local VotingModel model;

    model = VotingModel(__().memory.Allocate(class'VotingModel'));
    model.Start(drawMeansWin);
    return model;
}

protected static function SetVoters(
    VotingModel model,
    optional string voterID0,
    optional string voterID1,
    optional string voterID2,
    optional string voterID3,
    optional string voterID4,
    optional string voterID5,
    optional string voterID6,
    optional string voterID7,
    optional string voterID8,
    optional string voterID9
) {
    local UserID nextID;
    local array<UserID> voterIDs;

    if (voterID0 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID0));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID1 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID1));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID2 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID2));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID3 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID3));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID4 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID4));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID5 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID5));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID6 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID6));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID7 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID7));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID8 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID8));
        voterIDs[voterIDs.length] = nextID;
    }
    if (voterID9 != "") {
        nextID = UserID(__().memory.Allocate(class'UserID'));
        nextID.Initialize(__().text.FromString(voterID9));
        voterIDs[voterIDs.length] = nextID;
    }
    model.UpdatePotentialVoters(voterIDs);
}

protected static function MakeFaultyYesVote(
    VotingModel model,
    string voterID,
    VotingModel.VotingResult expected) {
    local UserID id;

    id = UserID(__().memory.Allocate(class'UserID'));
    id.Initialize(__().text.FromString(voterID));
    Issue("Illegal vote had unexpected result.");
    TEST_ExpectTrue(model.CastVote(id, true) == expected);
}

protected static function MakeFaultyNoVote(
    VotingModel model,
    string voterID,
    VotingModel.VotingResult expected) {
    local UserID id;

    id = UserID(__().memory.Allocate(class'UserID'));
    id.Initialize(__().text.FromString(voterID));
    Issue("Illegal vote had unexpected result.");
    TEST_ExpectTrue(model.CastVote(id, false) == expected);
}

protected static function VoteYes(VotingModel model, string voterID, ExpectedOutcome expected) {
    local UserID id;

    id = UserID(__().memory.Allocate(class'UserID'));
    id.Initialize(__().text.FromString(voterID));
    Issue("Failed to add legitimate vote.");
    TEST_ExpectTrue(model.CastVote(id, true) == VFR_Success);
    if (expected == TEST_EO_Continue) {
        Issue("Vote, that shouldn't have ended voting, ended it.");
        TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
    } else if (expected == TEST_EO_End) {
        Issue("Vote, that should've ended voting with one side's victory, didn't do it.");
        TEST_ExpectTrue(model.GetStatus() == VPM_Success);
    }
}

protected static function VoteNo(VotingModel model, string voterID, ExpectedOutcome expected) {
    local UserID id;

    id = UserID(__().memory.Allocate(class'UserID'));
    id.Initialize(__().text.FromString(voterID));
    Issue("Failed to add legitimate vote.");
    TEST_ExpectTrue(model.CastVote(id, false) == VFR_Success);
    if (expected == TEST_EO_Continue) {
        Issue("Vote, that shouldn't have ended voting, ended it.");
        TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
    } else if (expected == TEST_EO_End) {
        Issue("Vote, that should've ended voting with one side's victory, didn't do it.");
        TEST_ExpectTrue(model.GetStatus() == VPM_Failure);
    }
}

protected static function TESTS() {
    Test_VotingModel();
}

protected static function Test_VotingModel() {
    SubTest_YesVoting();
    SubTest_NoVoting();
    SubTest_FaultyVoting();
    SubTest_DisconnectVoting_DrawMeansWin();
    SubTest_DisconnectVoting_DrawMeansLoss();
    SubTest_ReconnectVoting_DrawMeansWin();
    SubTest_ReconnectVoting_DrawMeansLoss();
}

protected static function SubTest_YesVoting() {
    local VotingModel model;

    Context("Testing \"yes\" voting.");
    model = MakeVotingModel(true);
    SetVoters(model, "1", "2", "3", "4", "5", "6");
    VoteYes(model, "2", TEST_EO_Continue);
    VoteYes(model, "4", TEST_EO_Continue);
    VoteNo(model, "6", TEST_EO_Continue);
    VoteNo(model, "1", TEST_EO_Continue);
    VoteYes(model, "6", TEST_EO_End);

    model = MakeVotingModel(false);
    SetVoters(model, "1", "2", "3", "4", "5", "6");
    VoteYes(model, "2", TEST_EO_Continue);
    VoteYes(model, "4", TEST_EO_Continue);
    VoteYes(model, "3", TEST_EO_Continue);
    VoteNo(model, "6", TEST_EO_Continue);
    VoteNo(model, "1", TEST_EO_Continue);
    VoteYes(model, "6", TEST_EO_End);
}

protected static function SubTest_NoVoting() {
    local VotingModel model;

    Context("Testing \"no\" voting.");
    model = MakeVotingModel(true);
    SetVoters(model, "1", "2", "3", "4", "5", "6");
    VoteNo(model, "1", TEST_EO_Continue);
    VoteNo(model, "2", TEST_EO_Continue);
    VoteNo(model, "6", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteYes(model, "4", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_End);

    model = MakeVotingModel(false);
    SetVoters(model, "1", "2", "3", "4", "5", "6");
    VoteNo(model, "1", TEST_EO_Continue);
    VoteNo(model, "2", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteYes(model, "4", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_End);
}

protected static function SubTest_FaultyVoting() {
    local VotingModel model;

    Context("Testing \"faulty\" voting.");
    model = MakeVotingModel(false);
    SetVoters(model, "1", "2", "3", "4", "5", "6");
    VoteYes(model, "3", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteYes(model, "2", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_Continue);
    VoteYes(model, "1", TEST_EO_Continue);
    VoteYes(model, "6", TEST_EO_End);
    MakeFaultyYesVote(model, "4", VFR_VotingEnded); // voting already ended, so no more votes here

    model = MakeVotingModel(true);
    SetVoters(model, "1", "2", "3", "4", "5", "6");
    VoteYes(model, "3", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_Continue);
    VoteYes(model, "1", TEST_EO_Continue);
    VoteYes(model, "6", TEST_EO_End);
    MakeFaultyYesVote(model, "4", VFR_VotingEnded); // voting already ended, so no more votes here
}

protected static function SubTest_DisconnectVoting_DrawMeansWin() {
    local VotingModel model;

    Context("Testing \"disconnect\" voting when draw means victory.");
    model = MakeVotingModel(true);
    SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
    VoteYes(model, "1", TEST_EO_Continue);
    VoteYes(model, "2", TEST_EO_Continue);
    VoteYes(model, "3", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_Continue);
    VoteYes(model, "4", TEST_EO_Continue);
    VoteNo(model, "6", TEST_EO_Continue);
    SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 2 "yes" votes
    MakeFaultyNoVote(model, "1", VFR_NotAllowed);
    VoteNo(model, "8", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteNo(model, "9", TEST_EO_Continue);
    // Here we're at 3 "no" votes, 3 "yes" votes out of 7 total;
    // disconnect "6" and "9" for "yes" to win
    SetVoters(model, "2", "4", "5", "8", "10");
    Issue("Unexpected result after voting users disconnected.");
    TEST_ExpectTrue(model.GetStatus() == VPM_Success);
}

protected static function SubTest_DisconnectVoting_DrawMeansLoss() {
    local VotingModel model;

    Context("Testing \"disconnect\" voting when draw means loss.");
    model = MakeVotingModel(false);
    SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
    VoteYes(model, "1", TEST_EO_Continue);
    VoteYes(model, "2", TEST_EO_Continue);
    VoteYes(model, "6", TEST_EO_Continue);
    VoteYes(model, "3", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteNo(model, "5", TEST_EO_Continue);
    VoteYes(model, "4", TEST_EO_Continue);
    VoteNo(model, "6", TEST_EO_Continue);
    VoteYes(model, "7", TEST_EO_Continue);
    SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes
    MakeFaultyNoVote(model, "1", VFR_NotAllowed);
    VoteNo(model, "8", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteNo(model, "9", TEST_EO_Continue);
    // Here we're at 3 "no" votes, 3 "yes" votes out of 7 total;
    // disconnect "6" and "9" for "yes" to win
    SetVoters(model, "2", "4", "5", "8", "10");
    Issue("Unexpected result after voting users disconnected.");
    TEST_ExpectTrue(model.GetStatus() == VPM_Success);
}

protected static function SubTest_ReconnectVoting_DrawMeansWin() {
    local VotingModel model;

    Context("Testing \"reconnect\" voting when draw means victory.");
    model = MakeVotingModel(true);
    SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
    VoteYes(model, "1", TEST_EO_Continue);
    VoteNo(model, "2", TEST_EO_Continue);
    VoteYes(model, "3", TEST_EO_Continue);
    VoteNo(model, "4", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteNo(model, "6", TEST_EO_Continue);
    // Disconnect 1 3 "yes" voters
    SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10");
    VoteYes(model, "7", TEST_EO_Continue);
    VoteNo(model, "8", TEST_EO_Continue);
    VoteYes(model, "9", TEST_EO_Continue);
    MakeFaultyNoVote(model, "1", VFR_NotAllowed);
    MakeFaultyNoVote(model, "3", VFR_NotAllowed);
    // Restore 3 "yes" voter
    SetVoters(model, "2", "3", "4", "5", "6", "7", "8", "9", "10");
    VoteNo(model, "3", TEST_EO_End);
}

protected static function SubTest_ReconnectVoting_DrawMeansLoss() {
    local VotingModel model;

    Context("Testing \"reconnect\" voting when draw means loss.");
    model = MakeVotingModel(false);
    SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
    VoteYes(model, "1", TEST_EO_Continue);
    VoteNo(model, "2", TEST_EO_Continue);
    VoteYes(model, "3", TEST_EO_Continue);
    VoteNo(model, "4", TEST_EO_Continue);
    VoteYes(model, "5", TEST_EO_Continue);
    VoteNo(model, "6", TEST_EO_Continue);
    // Disconnect 1 3 "yes" voters
    SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10");
    VoteYes(model, "7", TEST_EO_Continue);
    VoteYes(model, "9", TEST_EO_Continue);
    MakeFaultyNoVote(model, "1", VFR_NotAllowed);
    MakeFaultyNoVote(model, "3", VFR_NotAllowed);
    // Restore 3 "yes" voter
    SetVoters(model, "2", "3", "4", "5", "6", "7", "8", "9", "10");
    VoteNo(model, "8", TEST_EO_Continue);
    VoteNo(model, "3", TEST_EO_End);
}

defaultproperties {
    caseGroup = "Commands"
    caseName = "Voting model"
}