/**
  *  \file u/t_game_vcr_classic_pvcralgorithm.cpp
  *  \brief Test for game::vcr::classic::PVCRAlgorithm
  */

#include "game/vcr/classic/pvcralgorithm.hpp"

#include "t_game_vcr_classic.hpp"
#include "afl/base/countof.hpp"
#include "game/vcr/classic/nullvisualizer.hpp"
#include "game/test/shiplist.hpp"
#include "game/vcr/classic/statustoken.hpp"

namespace {
    void initShipList(game::spec::ShipList& list)
    {
        game::test::initPListBeams(list);
        game::test::initPListTorpedoes(list);
    }

    struct Object {
        int beamType;
        int numBeams;
        int crew;
        int damage;
        int numBays;
        int numFighters;
        int hull;
        int id;
        int image;
        int experienceLevel;
        int mass;
        const char* name;
        int owner;
        int shield;
        int torpedoType;
        int numTorpedoes;
        int numLaunchers;
        int isPlanet;
        int beamKillRate;
        int beamChargeRate;
        int torpMissRate;
        int torpChargeRate;
        int crewDefenseRate;
    };
    struct Battle {
        int magic;
        uint16_t seed;
        int capabilities;
        const char* algorithm;
        Object object[2];
    };

    const Battle battles[] = {
        // tests/pvcr/vcr5.dat battle #1
        {18801,30078,0,"PHost 3",{{6,12,1300,0,14,150,99,143,144,0,751,"Bloody Mary",11,100,0,0,0,0,1,1,35,1,0},
                                  {10,15,787,0,0,0,35,2,68,0,681,"Cyc-9",4,100,10,85,13,0,1,1,35,1,0}}},

        // tests/pvcr/vcr5.dat battle #12
        {0,53958,0,"PHost 3",{{7,12,1249,0,14,129,99,492,144,0,751,"Alvilda the Goth",11,100,0,0,0,0,1,1,35,1,0},
                              {2, 2,   0,0, 3,  3, 0,425,  0,0,110,"Steenrod",        10,100,0,0,0,1,1,1,35,1,0}}},

        // Generated by c2systest/combat/05_asyftr.pl
        {25535,23344,3,"PHost 4", {{1,1,3100,0,20,1000,70,6,107,0,1110,"A",6,100,0,0,0,false,1,1,35,1,0},
                                   {1,1,3100,0,20,1000,70,3,107,0,1110,"B",3,100,0,0,0,false,1,1,35,1,0}}},
    };

    game::vcr::Object convertObject(const Object& in)
    {
        game::vcr::Object result;
        result.setMass(in.mass);
        result.setIsPlanet(in.isPlanet);
        result.setName(in.name);
        result.setDamage(in.damage);
        result.setCrew(in.crew);
        result.setId(in.id);
        result.setOwner(in.owner);
        result.setPicture(in.image);
        result.setHull(in.hull);
        result.setBeamType(in.beamType);
        result.setNumBeams(in.numBeams);
        result.setExperienceLevel(in.experienceLevel);
        result.setNumBays(in.numBays);
        result.setTorpedoType(in.torpedoType);
        result.setNumTorpedoes(in.numTorpedoes);
        result.setNumFighters(in.numFighters);
        result.setNumLaunchers(in.numLaunchers);
        result.setShield(in.shield);
        result.setBeamKillRate(in.beamKillRate);
        result.setBeamChargeRate(in.beamChargeRate);
        result.setTorpMissRate(in.torpMissRate);
        result.setTorpChargeRate(in.torpChargeRate);
        result.setCrewDefenseRate(in.crewDefenseRate);
        return result;
    }

    void initConfig(game::config::HostConfiguration& config)
    {
        config[config.AllowAlternativeCombat].set(1);
        config[config.BayLaunchInterval].set(2);
        config[config.BayRechargeBonus].set(1);
        config[config.BayRechargeRate].set(40);
        config[config.BeamFiringRange].set(25000);
        config[config.BeamHitBonus].set(12);
        config[config.BeamHitFighterCharge].set(500);
        config[config.BeamHitFighterRange].set(100000);
        config[config.BeamHitOdds].set(70);
        config[config.BeamHitShipCharge].set(600);
        config[config.BeamRechargeBonus].set(4);
        config[config.BeamRechargeRate].set(4);
        config[config.CrewKillScaling].set(30);
        config[config.EModBayRechargeBonus].set(0);
        config[config.EModBayRechargeRate].set("1,2,3,4,4,4,4,4,4,4");
        config[config.EModBeamHitBonus].set(0);
        config[config.EModBeamHitFighterCharge].set(0);
        config[config.EModBeamHitOdds].set(0);
        config[config.EModBeamRechargeBonus].set(0);
        config[config.EModBeamRechargeRate].set(0);
        config[config.EModCrewKillScaling].set("-5,-10,-15,-20,-20,-20,-20,-20,-20,-20");
        config[config.EModFighterBeamExplosive].set(0);
        config[config.EModFighterBeamKill].set(0);
        config[config.EModFighterMovementSpeed].set(0);
        config[config.EModHullDamageScaling].set(0);
        config[config.EModMaxFightersLaunched].set(0);
        config[config.EModShieldDamageScaling].set(0);
        config[config.EModShieldKillScaling].set(0);
        config[config.EModStrikesPerFighter].set("1,2,3,4,4,4,4,4,4,4");
        config[config.EModTorpHitBonus].set(0);
        config[config.EModTorpHitOdds].set("9,18,27,35,35,35,35,35,35,35");
        config[config.EModTubeRechargeBonus].set(0);
        config[config.EModTubeRechargeRate].set("1,2,3,8,8,8,8,8,8,8");
        config[config.FighterBeamExplosive].set(8);
        config[config.FighterBeamKill].set(8);
        config[config.FighterFiringRange].set(3000);
        config[config.FighterKillOdds].set(10);
        config[config.FighterMovementSpeed].set(300);
        config[config.FireOnAttackFighters].set(1);
        config[config.HullDamageScaling].set(20);
        config[config.MaxFightersLaunched].set(30);
        config[config.PlayerRace].set("1,2,3,4,5,6,7,8,9,10,11");
        config[config.ShieldDamageScaling].set(40);
        config[config.ShieldKillScaling].set(0);
        config[config.ShipMovementSpeed].set(100);
        config[config.StandoffDistance].set(10000);
        config[config.StrikesPerFighter].set(5);
        config[config.TorpFiringRange].set(30000);
        config[config.TorpHitBonus].set(13);
        config[config.TorpHitOdds].set(55);
        config[config.TubeRechargeBonus].set(7);
        config[config.TubeRechargeRate].set(30);
    }
}

void
TestGameVcrClassicPVCRAlgorithm::testTF()
{
    // Surroundings
    game::vcr::classic::NullVisualizer vis;
    game::config::HostConfiguration config;
    game::spec::ShipList list;
    initShipList(list);
    initConfig(config);

    // First fight
    game::vcr::classic::PVCRAlgorithm testee(false, vis, config, list.beams(), list.launchers());
    game::vcr::Object left(convertObject(battles[0].object[0]));
    game::vcr::Object right(convertObject(battles[0].object[1]));
    uint16_t seed = battles[0].seed;
    bool result = testee.checkBattle(left, right, seed);
    TS_ASSERT(!result);

    testee.initBattle(left, right, seed);
    while (testee.playCycle()) {
        // nix
    }
    testee.doneBattle(left, right);

    // Record #1:
    //         Ending time 410 (6:50)
    //         left-destroyed
    //   S:  0  D:100  C:1241  A: 92   |     S:  0  D: 61  C:698  A: 14
    TS_ASSERT_EQUALS(testee.getTime(), 410);
    TS_ASSERT(testee.getResult().contains(game::vcr::classic::LeftDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::LeftCaptured));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightCaptured));
    TS_ASSERT_EQUALS(left.getDamage(), 100);
    TS_ASSERT_EQUALS(right.getDamage(), 61);
    TS_ASSERT_EQUALS(left.getShield(), 0);
    TS_ASSERT_EQUALS(right.getShield(), 0);
    TS_ASSERT_EQUALS(left.getCrew(), 1241);
    TS_ASSERT_EQUALS(right.getCrew(), 698);
    TS_ASSERT_EQUALS(left.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(right.getNumTorpedoes(), 14);
    TS_ASSERT_EQUALS(left.getNumFighters(), 92);
    TS_ASSERT_EQUALS(right.getNumFighters(), 0);
    TS_ASSERT_EQUALS(testee.getStatistic(game::vcr::classic::LeftSide).getNumFights(), 1);
    TS_ASSERT_EQUALS(testee.getStatistic(game::vcr::classic::RightSide).getNumFights(), 1);
}

void
TestGameVcrClassicPVCRAlgorithm::testCarriers()
{
    // Surroundings
    game::vcr::classic::NullVisualizer vis;
    game::config::HostConfiguration config;
    game::spec::ShipList list;
    initShipList(list);
    initConfig(config);

    // First fight
    game::vcr::classic::PVCRAlgorithm testee(false, vis, config, list.beams(), list.launchers());
    game::vcr::Object left(convertObject(battles[1].object[0]));
    game::vcr::Object right(convertObject(battles[1].object[1]));
    uint16_t seed = battles[1].seed;
    bool result = testee.checkBattle(left, right, seed);
    TS_ASSERT(!result);

    testee.initBattle(left, right, seed);
    while (testee.playCycle()) {
        // nix
    }
    testee.doneBattle(left, right);

    // Record #12: (two fighter units)
    //         Ending time 245 (4:05)
    //         right-destroyed
    //   S:100  D:  0  C:1249  A:127   |     S:  0  D:100  C:  0  A:  0
    TS_ASSERT_EQUALS(testee.getTime(), 245);
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::LeftDestroyed));
    TS_ASSERT(testee.getResult().contains(game::vcr::classic::RightDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::LeftCaptured));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightCaptured));
    TS_ASSERT_EQUALS(left.getDamage(), 0);
    TS_ASSERT_EQUALS(right.getDamage(), 100);
    TS_ASSERT_EQUALS(left.getShield(), 100);
    TS_ASSERT_EQUALS(right.getShield(), 0);
    TS_ASSERT_EQUALS(left.getCrew(), 1249);
    TS_ASSERT_EQUALS(right.getCrew(), 0);
    TS_ASSERT_EQUALS(left.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(right.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(left.getNumFighters(), 127);
    TS_ASSERT_EQUALS(right.getNumFighters(), 0);
}

/** Test behaviour if bonus computation overflows 100%.

    PCC2 bug #304: With the wrong implementation of randomRange100LT, beams fail to hit once the effective BeamHitOdds goes over 100.
    This means a ship with numerically better beams will perform much worse than expected,
    which gets especially visible against fighters where all beams do essentially the same damage.

    This test sets up two otherwise identical ships, one with type-1 beams, one with type-10.
    The expectation is that the type-10 ship wins. */
void
TestGameVcrClassicPVCRAlgorithm::testRandomBonus()
{
    // Surroundings
    game::vcr::classic::NullVisualizer vis;
    game::config::HostConfiguration config;
    game::spec::ShipList list;
    initShipList(list);
    initConfig(config);

    // BeamHitOdds = 88
    // BeamHitBonus = 12
    //  --> beam 1:   (1+2)*12/100   = 0   -> 88
    //  --> beam 10:  (40+80)*12/100 = 14  -> 102
    config[config.BeamHitOdds].set(88);

    //                                bt, bc  crew dmg bay ftr ht id im exp mass         ow  shld tt tc lc planet ------nu------
    const struct Object leftShip  = {  1, 10, 1000, 0, 8, 100, 1, 1, 1,  0, 400, "Left",  6, 100, 0, 0, 0, false, 1, 1, 35, 1, 0 };
    const struct Object rightShip = { 10, 10, 1000, 0, 8, 100, 1, 1, 1,  0, 400, "Right", 7, 100, 0, 0, 0, false, 1, 1, 35, 1, 0 };

    // First fight
    game::vcr::classic::PVCRAlgorithm testee(false, vis, config, list.beams(), list.launchers());
    game::vcr::Object left(convertObject(leftShip));
    game::vcr::Object right(convertObject(rightShip));
    uint16_t seed = 0;
    bool result = testee.checkBattle(left, right, seed);
    TS_ASSERT(!result);

    testee.initBattle(left, right, seed);
    while (testee.playCycle()) {
        // nix
    }
    testee.doneBattle(left, right);

    TS_ASSERT_EQUALS(testee.getTime(), 617);
    TS_ASSERT(testee.getResult().contains(game::vcr::classic::LeftDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::LeftCaptured));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightCaptured));
    TS_ASSERT_EQUALS(left.getDamage(), 100);
    TS_ASSERT_EQUALS(right.getDamage(), 53);
    TS_ASSERT_EQUALS(left.getShield(), 0);
    TS_ASSERT_EQUALS(right.getShield(), 0);
    TS_ASSERT_EQUALS(left.getCrew(), 851);
    TS_ASSERT_EQUALS(right.getCrew(), 921);
    TS_ASSERT_EQUALS(left.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(right.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(left.getNumFighters(), 20);
    TS_ASSERT_EQUALS(right.getNumFighters(), 61);
}

/** Test token handling. */
void
TestGameVcrClassicPVCRAlgorithm::testToken()
{
    // Surroundings
    game::vcr::classic::NullVisualizer vis;
    game::config::HostConfiguration config;
    game::spec::ShipList list;
    initShipList(list);
    initConfig(config);

    // Initialize fight (same as testCarriers)
    game::vcr::classic::PVCRAlgorithm testee(false, vis, config, list.beams(), list.launchers());
    game::vcr::Object left(convertObject(battles[1].object[0]));
    game::vcr::Object right(convertObject(battles[1].object[1]));
    uint16_t seed = battles[1].seed;
    bool result = testee.checkBattle(left, right, seed);
    TS_ASSERT(!result);

    // Run 100 ticks and take a token (fight is 245 ticks long)
    testee.initBattle(left, right, seed);
    for (int i = 0; i < 100; ++i) {
        TS_ASSERT(testee.playCycle());
    }
    TS_ASSERT_EQUALS(testee.getTime(), 100);
    std::auto_ptr<game::vcr::classic::StatusToken> tok(testee.createStatusToken());
    TS_ASSERT(tok.get() != 0);
    TS_ASSERT_EQUALS(tok->getTime(), 100);

    // Run to end
    while (testee.playCycle()) {
        // nix
    }
    testee.doneBattle(left, right);
    TS_ASSERT_EQUALS(testee.getTime(), 245);

    // Rewind to token
    testee.restoreStatus(*tok);
    TS_ASSERT_EQUALS(testee.getTime(), 100);

    // Run to end again
    while (testee.playCycle()) {
        // nix
    }
    testee.doneBattle(left, right);
    TS_ASSERT_EQUALS(testee.getTime(), 245);

    // Verify result (see testCarriers)
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::LeftDestroyed));
    TS_ASSERT(testee.getResult().contains(game::vcr::classic::RightDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::LeftCaptured));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightCaptured));
    TS_ASSERT_EQUALS(left.getDamage(), 0);
    TS_ASSERT_EQUALS(right.getDamage(), 100);
    TS_ASSERT_EQUALS(left.getShield(), 100);
    TS_ASSERT_EQUALS(right.getShield(), 0);
    TS_ASSERT_EQUALS(left.getCrew(), 1249);
    TS_ASSERT_EQUALS(right.getCrew(), 0);
    TS_ASSERT_EQUALS(left.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(right.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(left.getNumFighters(), 127);
    TS_ASSERT_EQUALS(right.getNumFighters(), 0);
}

/** Test asymmetric fighter configuration. */
void
TestGameVcrClassicPVCRAlgorithm::testAsymmetricFighterConfig()
{
    // Surroundings
    game::vcr::classic::NullVisualizer vis;
    game::config::HostConfiguration config;
    game::spec::ShipList list;
    initShipList(list);

    // Config (from came that created this testcase, i.e. c2systest/combat/05_asyftr.pl)
    config[config.AllowAlternativeCombat].set(1);
    config[config.AllowEngineShieldBonus].set(1);
    config[config.BayLaunchInterval].set(2);
    config[config.BayRechargeBonus].set(1);
    config[config.BayRechargeRate].set("40,42,40,39,36,41,40,40,37,42,40");
    config[config.BeamFiringRange].set(25000);
    config[config.BeamHitBonus].set("12,12,12,13,12,12,12,12,10,13,12");
    config[config.BeamHitFighterCharge].set("460,460,460,460,460,440,460,460,450,500,430");
    config[config.BeamHitFighterRange].set(100000);
    config[config.BeamHitOdds].set("70,70,70,70,70,70,70,70,66,71,70");
    config[config.BeamHitShipCharge].set("600,600,600,600,600,570,600,600,630,580,550");
    config[config.BeamRechargeBonus].set("4,4,4,5,4,3,4,4,6,4,4");
    config[config.BeamRechargeRate].set("4,5,4,6,5,3,4,4,6,4,4");
    config[config.CrewKillScaling].set("30,20,30,30,30,39,15,30,30,30,30");
    config[config.EModBayRechargeBonus].set(0);
    config[config.EModBayRechargeRate].set("4,8,5,0");
    config[config.EModBeamHitBonus].set("2,2,3,5");
    config[config.EModBeamHitFighterCharge].set(0);
    config[config.EModBeamHitOdds].set("4,4,5,8");
    config[config.EModBeamRechargeBonus].set("0,0,1,1");
    config[config.EModBeamRechargeRate].set("0,1,1,2");
    config[config.EModCrewKillScaling].set("-6,-9,-12,-15");
    config[config.EModEngineShieldBonusRate].set("3,6,9,12");
    config[config.EModExtraFighterBays].set("0,0,1,1");
    config[config.EModFighterBeamExplosive].set(0);
    config[config.EModFighterBeamKill].set(0);
    config[config.EModFighterMovementSpeed].set(0);
    config[config.EModHullDamageScaling].set(0);
    config[config.EModMaxFightersLaunched].set("2,4,4,4");
    config[config.EModShieldDamageScaling].set(0);
    config[config.EModShieldKillScaling].set(0);
    config[config.EModStrikesPerFighter].set("0,0,0,1");
    config[config.EModTorpHitBonus].set("1,2,3,4");
    config[config.EModTorpHitOdds].set("1,2,3,5");
    config[config.EModTubeRechargeBonus].set("1,1,2,3");
    config[config.EModTubeRechargeRate].set("1,2,3,5");
    config[config.FighterBeamExplosive].set("9,9,9,10,9,9,10,9,10,6,9");
    config[config.FighterBeamKill].set("9,12,9,10,9,9,13,9,8,6,9");
    config[config.FighterFiringRange].set("3000,3000,2800,2800,3000,3300,2900,3200,3000,3000,3000");
    config[config.FighterKillOdds].set("30,30,30,30,10,10,10,10,10,10,10");     // This is the option under test!
    config[config.FighterMovementSpeed].set("300,310,300,290,300,300,300,290,275,300,320");
    config[config.FireOnAttackFighters].set(1);
    config[config.HullDamageScaling].set("20,20,20,21,20,20,20,20,18,20,20");
    config[config.MaxFightersLaunched].set("0,30,30,30,30,30,30,32,30,30,30");
    config[config.NumExperienceLevels].set(4);
    config[config.PlayerRace].set("1,2,3,4,5,6,7,8,9,10,11");
    config[config.ShieldDamageScaling].set("40,40,48,40,36,40,40,40,60,36,40");
    config[config.ShieldKillScaling].set(0);
    config[config.ShipMovementSpeed].set("100,100,100,100,110,90,100,100,90,100,100");
    config[config.StandoffDistance].set(10000);
    config[config.StrikesPerFighter].set("5,5,5,4,5,5,5,5,5,7,5");
    config[config.TorpFiringRange].set(30000);
    config[config.TorpHitBonus].set("13,13,14,13,10,13,13,13,7,12,11");
    config[config.TorpHitOdds].set("50,50,52,52,47,52,61,51,42,49,44");
    config[config.TubeRechargeBonus].set("7,7,8,7,8,7,4,6,9,6,8");
    config[config.TubeRechargeRate].set("30,29,31,30,31,30,27,29,34,29,31");

    // Patch up ship list
    list.beams().get(1)->setKillPower(1);
    list.beams().get(1)->setDamagePower(7);
    list.beams().get(1)->cost().set(game::spec::Cost::Money, 2);
    list.beams().get(1)->cost().set(game::spec::Cost::Tritanium, 0);
    list.beams().get(1)->cost().set(game::spec::Cost::Duranium, 1);
    list.beams().get(1)->cost().set(game::spec::Cost::Molybdenum, 1);
    list.beams().get(1)->setMass(0);
    list.beams().get(1)->setTechLevel(1);

    // Initialize fight
    game::vcr::classic::PVCRAlgorithm testee(true, vis, config, list.beams(), list.launchers());
    game::vcr::Object left(convertObject(battles[2].object[0]));
    game::vcr::Object right(convertObject(battles[2].object[1]));
    uint16_t seed = battles[2].seed;
    bool result = testee.checkBattle(left, right, seed);
    TS_ASSERT(!result);

    // Run
    testee.initBattle(left, right, seed);
    while (testee.playCycle()) {
        // nix
    }
    testee.doneBattle(left, right);
    TS_ASSERT_EQUALS(testee.getTime(), 576);

    // Verify result
    TS_ASSERT(testee.getResult().contains(game::vcr::classic::LeftDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightDestroyed));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::LeftCaptured));
    TS_ASSERT(!testee.getResult().contains(game::vcr::classic::RightCaptured));
    TS_ASSERT_EQUALS(left.getDamage(), 100);
    TS_ASSERT_EQUALS(right.getDamage(), 91);
    TS_ASSERT_EQUALS(left.getShield(), 0);
    TS_ASSERT_EQUALS(right.getShield(), 0);
    TS_ASSERT_EQUALS(left.getCrew(), 2906);
    TS_ASSERT_EQUALS(right.getCrew(), 2964);
    TS_ASSERT_EQUALS(left.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(right.getNumTorpedoes(), 0);
    TS_ASSERT_EQUALS(left.getNumFighters(), 880);
    TS_ASSERT_EQUALS(right.getNumFighters(), 963);
}
