"use strict";
this.name = "PlanetFall2_MonkeyPatch";
this.author = "phkb";
this.copyright = "Creative Commons Attribution - Non-Commercial - Share Alike 3.0 license with clauses - see readme.txt.";
this.description = "Script that updates other scripts for compatibility";

/*
    Note: For Aquatics, we've moved the setting of planetFallOverride into the systemWillPopulate world event.
    However, there is no guarantee by default that those scripts will be run before the systemWillPopulate function in Additional Planets.
    This is a problem because, if the planetFallOverride isn't set yet, all the default landing points will be generated.
    We could move the planetFallOverride setting to be in shipWillEnterWitchspace, but that doesn't help when loading a new game.
    Instead, we've created a callback process, where functions can be entered and then called before the Additional Planets 
    systemWillPopulate function, which ensures they will happen at the right time.
*/
this.debug = false;
this.planetLandConflict = false;

//-------------------------------------------------------------------------------------------------------------
this.startUp = function () {
    var pf = worldScripts.PlanetFall2;

    // this is so we can insert our code to add planetary docks at the same time a planet is created
    if (worldScripts["System Redux"]) {
        if (this.debug) log(this.name, "Monkey-patching systemWillPopulate and $addPlanets functions of Additional Planets");
        worldScripts["System Redux"].systemWillPopulate = this.sysredux_systemWillPopulate;
        worldScripts["System Redux"].$addPlanets = this.sysredux_$addPlanets;
    }
    // only monkey patch Planetary Systems if the SWBeforePopulators OXP is present.
    if (worldScripts["PlanetOrbits"] && worldScripts["SWBeforePopulators_main"]) {
        if (this.debug) log(this.name, "Monkey-patching addPlanets function of Planetary Systems");
        worldScripts["PlanetOrbits"].addPlanets = this.planetarysystems_addPlanets;
    }
    // aquatics 
    if (worldScripts.aquatics_populator) {
        pf.$addPrepopulationFunction("aquatics_populator", "extraPopulation");
        if (this.debug) log(this.name, "Monkey-patching shipWillEnterWitchspace and shipWillLaunchFromStation functions of Aquatics populator script");
        var a = worldScripts.aquatics_populator;
        a.extraPopulation = this.aquatics_extraPopulation;
        a.shipWillEnterWitchspace = this.aquatics_shipWillEnterWitchspace
        a.shipWillLaunchFromStation = this.aquatics_shipWillLaunchFromStation;
    }
    // in-system cargo delivery
    if (worldScripts["In-System Cargo Delivery"]) {
        if (this.debug) log(this.name, "Monkey-patching $missionActualInfo function of In-System Cargo Delivery");
        worldScripts["In-System Cargo Delivery"].$missionActualInfo = this.insystemdelivery_missionActualInfo;
    }
}

//-------------------------------------------------------------------------------------------------------------
this.missionScreenOpportunity = function() {
    if (!worldScripts.Planet_Makeup) {
        delete this.missionScreenOpportunity;
        return;
    }
    if (!this.planetLandConflict) {
        this.planetLandConflict = true;
        mission.runScreen({
            title: expandMissionText("planetFall2_planetLand_title"),
            message: expandMissionText("planetFall2_planetLand_conflict"),
            overlay: {name:"planetfall-warning.png", height:546}
        });
    }
}

//-------------------------------------------------------------------------------------------------------------
this.sysredux_systemWillPopulate = function () {
    //sort the pools on first launch so that the textures stay consistently regardless of the load order of the texture packs
    if (this.runOnce) {
        this.$planetPool.sort();
        this.$moonPool.sort();
        this.runOnce = false;
    }

    // make sure some things are run before all planets are set up
    worldScripts.PlanetFall2.$prepopulationControls();

    if (this.excl[galaxyNumber].indexOf(system.ID) === -1) {
        //init station array in sfap oxp
        if (worldScripts["stations_for_extra_planets"])
            worldScripts["stations_for_extra_planets"].initStationArray();
        this.$addPlanets();
    }
}

//-------------------------------------------------------------------------------------------------------------
// injecting links to additionally spawn planetary locations when spawning planets and moons
this.sysredux_$addPlanets = function () {
    //temporary arrays to be used with splice so that no two planet/moons/giant get the same texture in one system
    var tempPlanets = new Array();
    tempPlanets = tempPlanets.concat(this.$planetPool);
    var tempMoons = new Array();
    tempMoons = tempMoons.concat(this.$moonPool);
    var tempGiants = new Array();
    tempGiants = tempGiants.concat(this.$giantPool);
    var usedTextures = new Array();

    //function to manage textures so that if the same texture is used as moon and planet, it won't get shown twice in the same system
    function getTexture(pool, seed) {
        while (true) {
            if (pool.length !== 0) {
                var texNum = Math.floor(system.scrambledPseudoRandomNumber(seed) * pool.length);
                var texture = pool.splice(texNum, 1)[0];
                if (usedTextures.indexOf(texture) === -1) {
                    usedTextures.push(texture);
                    return texture;
                }
            }
            else return null;
        }
    }

    //function to add moons around given planet
    function addMoons(planet_position, planet_radius, seed) {
        //number of moon sizes defined in planetinfo.plist
        var moonSizes = 4;

        //number of moons to be added. extremes have probability weight of 0.5, others 1.
        var numberOfMoons = Math.round(system.scrambledPseudoRandomNumber(seed) * worldScripts["System Redux"].max_moons);

        //orbits. lowest is 2.5 * planet radius. station is usually at 2 * planet radius.
        var moonOrbits = new Array();
        for (var i = 0; i < worldScripts["System Redux"].max_moons; i++) moonOrbits.push(i);
        var baseMoonOrbit = planet_radius * worldScripts["System Redux"].moon_mult;
        var moonOrbitDifference = 30000;

        //for cinematic reasons moons are positioned so that when you view the main planet from wp, you should be able to see all the moons around it.
        //wp-planet line is kept empty.
        for (var i = 0; i < numberOfMoons; i++) {

            //texture
            var moonTexture = getTexture(tempMoons, system.scrambledPseudoRandomNumber(2 * (seed + i)));
            if (moonTexture === null) return;//if we're out of textures, abort.

            //body
            var moonBody = "ap-moon" + Math.floor(system.scrambledPseudoRandomNumber(3 * (seed + i)) * moonSizes);

            //position
            var moonOrbit = moonOrbits.splice(Math.floor(system.scrambledPseudoRandomNumber(11 * (seed + i)) * moonOrbits.length), 1)[0];
            var polar = Math.acos(1.4 * system.scrambledPseudoRandomNumber(5 * (seed + i)) - 0.7);
            var azimuth = 2.0 * Math.PI * system.scrambledPseudoRandomNumber(7 * (seed + i));
            var directionV = Vector3D(Math.sin(polar) * Math.cos(azimuth), Math.sin(polar) * Math.sin(azimuth), Math.cos(polar));
            var distance = baseMoonOrbit + moonOrbitDifference * moonOrbit;
            var moonPosition = planet_position.toCoordinateSystem("pwm").add(directionV.multiply(distance)).fromCoordinateSystem("pwm");

            setPopulatorMoon(moonTexture, moonBody, moonPosition);
        }
    }

    function setPopulatorPlanet(texture, body, coords, giant, radius) {
        system.setPopulator("ap_planet" + texture, {
            callback: function (pos) {
                var addedPlanet = system.addPlanet(body);
                addedPlanet.texture = texture;
                if (giant) {
                    //for planetfall
                    addedPlanet.solarGasGiant = true;
                    //for gas giant skimmer
                    addedPlanet.isGasGiant = true;
                }
                addedPlanet.position = pos;
            }.bind(this),
            location: "COORDINATES",
            coordinates: coords,
            priority: 100
        });
        //add surface docks
        if (!giant) {
            worldScripts.PlanetFall2.$addExtraPlanetDocks(coords, radius, true, false);
        }
        //add station to planets with atmosphere
        if (worldScripts["stations_for_extra_planets"] && !giant)
            worldScripts["stations_for_extra_planets"].setStationPopulator(coords, radius);
    }

    function setPopulatorMoon(texture, body, coords) {
        system.setPopulator("ap_moon" + texture, {
            callback: function (pos) {
                var addedMoon = system.addMoon(body);
                addedMoon.texture = texture;
                addedMoon.position = pos;
            }.bind(this),
            location: "COORDINATES",
            coordinates: coords,
            priority: 101
        });
        var radius = 0;
        switch (body) {
            case "ap-moon0": radius = 7000; break;
            case "ap-moon1": radius = 8000; break;
            case "ap-moon2": radius = 9000; break;
            case "ap-moon3": radius = 10000; break;
        }
        worldScripts.PlanetFall2.$addExtraPlanetDocks(coords, radius, false, false);
    }

    //all extra planets are positioned on incrementing distances calculated from the main planet. baseOrbit is the distance of the closest possible planet and orbitDifference is the increment from there on. These are multiples of unit calculated from wp-planet distance, so they differ from system to system. unit range is ~ 300 km - 400 km. For reference wp-planet range is ~ 300 km - 900 km.
    var orbits = new Array();
    for (var i = 0; i < this.max_planets; i++) orbits.push(i);
    var baseUnit = 1.0 / 6.0 * system.mainPlanet.position.magnitude() + 250000;
    var baseOrbit = this.planet_mult * baseUnit;//I would not go under 5. With 4, it's possible that an added planet is as close to the wp buoy as the main planet.
    var orbitDifference = 2.5 * baseUnit;

    //the number of extra planets in system. using Math.round gives smaller probabilities to the extremes, 0 and this.max_planets. With max planets 4, you'll be seeing more 1-3 extra planet systems than 0 or 4 extra planet systems. Probabitity weights are 0.5, 1, 1, 1, 0.5.
    var planetsInSystem = Math.round(system.pseudoRandomNumber * this.max_planets);

    for (var i = 0; i < planetsInSystem; i++) {

        //orbit.
        var orbit = orbits.splice(Math.floor(system.scrambledPseudoRandomNumber(galaxyNumber + 7 * (i + 1)) * orbits.length), 1)[0];
        //planet size. there are 9+1 different sizes defined in planetinfo.plist. 10th is reserved for gas giants that can only appear on outer orbits.
        var planetSizes = 9;
        if (orbit > 1 && tempGiants.length > 0) planetSizes = 10;
        var planetInd = Math.floor(system.scrambledPseudoRandomNumber(galaxyNumber + 2 * (i + 1)) * planetSizes);

        //texture
        if (planetInd === 9)
            var planetTexture = getTexture(tempGiants, system.scrambledPseudoRandomNumber(galaxyNumber + 11 * (i + 1)));
        else
            var planetTexture = getTexture(tempPlanets, system.scrambledPseudoRandomNumber(galaxyNumber + 11 * (i + 1)));
        if (planetTexture === null) continue; //if we're out of textures, skip to the next iteration.

        //body
        var planetBody = "ap-planet" + planetInd;
        if (planetInd === 9) var giant = true;
        else var giant = false;

        //position. planet is placed on a zone of a sphere around the main planet opposite to the sun.
        var polar = Math.acos(1.4 * system.scrambledPseudoRandomNumber(galaxyNumber + 5 * (i + 1)) - 1);
        var azimuth = 2.0 * Math.PI * system.scrambledPseudoRandomNumber(galaxyNumber + 3 * (i + 1));
        var directionV = Vector3D(Math.sin(polar) * Math.cos(azimuth), Math.sin(polar) * Math.sin(azimuth), Math.cos(polar));
        var distance = baseOrbit + orbitDifference * orbit;
        var planetPosition = directionV.multiply(distance).fromCoordinateSystem("psm");

        var radiuses = [5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 20000];
        setPopulatorPlanet(planetTexture, planetBody, planetPosition, giant, radiuses[planetInd] * 10);


        //moons for extra planets
        if (system.scrambledPseudoRandomNumber(galaxyNumber + 17 * (i + 1)) < this.extraPlanetMoonsProbability) {
            //positioning depends on planet radius. Moon populator is set before the planet is added. Hence radiuses are given here.
            addMoons(planetPosition, radiuses[planetInd] * 10, galaxyNumber + 13);
        }
    }
    addMoons(system.mainPlanet.position, system.mainPlanet.radius, galaxyNumber + 19);
}

//-------------------------------------------------------------------------------------------------------------
this.planetarysystems_addPlanets = function (id) {
    // Add planets in system dependent order. Id is used here in two
    // ways:
    // - Its lower bits determine the planets in planetinfo.plist
    // - It represents a permutation and hence the order how the selected
    //   planets are added (see wikipedia for factoradic) and Entity-IDs
    //   are assigned.

    var selectPlanet1 = system.scrambledPseudoRandomNumber(163);
    var selectPlanet2 = system.scrambledPseudoRandomNumber(171);
    var selectPlanet3 = system.scrambledPseudoRandomNumber(179);
    var selectPlanet4 = system.scrambledPseudoRandomNumber(187);
    var selectPlanet5 = system.scrambledPseudoRandomNumber(195);
    var selectPlanet6 = system.scrambledPseudoRandomNumber(203);
    var selectPlanet7 = system.scrambledPseudoRandomNumber(211);
    var selectPlanet8 = system.scrambledPseudoRandomNumber(219);

    var selectPlanetData = [
        selectPlanet1,
        selectPlanet2,
        selectPlanet3,
        selectPlanet4,
        selectPlanet5,
        selectPlanet6,
        selectPlanet7,
        selectPlanet8
    ];

    var v = new Array(this.MaxPlanets), w = new Array(this.MaxPlanets);
    // Avoid huge factorials by grouping up to 12 planets.
    var mask = id;
    for (var i = 0; i < this.MaxPlanets && mask != 0 && id != 0;) {
        var m = 0, f = 1;
        // Type conversion prohibits use of the shift operator. Kind
        // of weird to apply "&" on a floating point number!
        for (; m < 12 && i < this.MaxPlanets; ++i, mask = Math.floor(mask / 2))
            if ((mask & 1) != 0)
                v[m] = i, w[m] = m, f *= ++m;
        var fn = id % f;	// fn = sum(j in [0..m-1]:j!*c[j]), c[j] <= j
        id = Math.floor(id / f);
        for (; m > 0; --m) {
            f /= m;
            var c = Math.floor(fn / f);
            fn -= f * c;
            this.Log(this.Verbose,
                "" + m + ":selecting oplanet" +
                (v[w[c]] + 1) + "," + w[c] + "," +
                c + "," + f + "," + fn);

            var addPlanetTagIn = v[w[c]];
            var addPlanetTagOut = 10 * (addPlanetTagIn + 1) + Math.floor(selectPlanetData[addPlanetTagIn] * 10);

            var pl = system.addPlanet("oplanet" + addPlanetTagOut);
            worldScripts.PlanetFall2.$addExtraPlanetDocksForPlanet(pl);

            // system.addPlanet("oplanet" + (v[w[c]] + 1));
            for (; c + 1 < m; ++c)
                w[c] = w[c + 1];
        }
    }
}

//-------------------------------------------------------------------------------------------------------------
this.aquatics_extraPopulation = function () {
    if (galaxyNumber == 2 && system.ID == 99 && worldScripts.PlanetFall2) { // if Planetfall is loaded
        worldScripts.PlanetFall2.planetFallOverride = true;  // set the override, so only external OXP locations appear 
    }
}

//-------------------------------------------------------------------------------------------------------------
this.aquatics_shipWillEnterWitchspace = function () {
    this.platformMessage = null;
    this.launchFlag = null;

    if (galaxyNumber == 2 && system.ID == 99 && worldScripts.PlanetFall2) {// if Planetfall is loaded and we're leaving Aqualina
        worldScripts.PlanetFall2.planetFallOverride = false; // reset the override 
    }

    if (missionVariables.aquatics_krakenMission == "BEGUN") {
        missionVariables.aquatics_krakenMission = "FAILED";
        mission.setInstructionsKey(null);
    }
}

//-------------------------------------------------------------------------------------------------------------
// all we're doing with this function is removing the planetfall integration code. it's now all in systemWillPopulate
this.aquatics_shipWillLaunchFromStation = function (station) {
    this.launchFlag = true;

    if (galaxyNumber == 2 && system.ID == 99 && station.hasRole("aquatics_HQ")) {
        if (missionVariables.aquatics_krakenMission == "BRIEFING_1" || missionVariables.aquatics_krakenMission == "BRIEFING_2" || missionVariables.aquatics_krakenMission == "IMMEDIATE_OFFER")
            missionVariables.aquatics_krakenMission = "MAKE_OFFER";

        if (missionVariables.aquatics_krakenMission == "NOT_WELCOME" || missionVariables.aquatics_krakenMission == "KICK_OUT")
            missionVariables.aquatics_krakenMission = "CASE_CLOSED";

        if (missionVariables.aquatics_krakenMission == "BEGUN" && system.countShipsWithRole("aquatics_kraken") == 0)
            this.addKraken();
    }
}

//-------------------------------------------------------------------------------------------------------------
// the planetfall equipment item (EQ_PLANETFALL) is now optional, so we're tweaking this code to remove it as 
// an option when checking if the player is equipped for a mission
this.insystemdelivery_missionActualInfo = function () {
    var u = -1;
    var s = -1;
    this.$destinationPort = system.info.name + " " + this.$planetInfoSelected;
    var deliveryInfo = "";
    // emergency missions
    // *** item selection
    if (this.$urgentMissionFlag >= 0) {
        u = this.$urgentMissionFlag;
        this.$urgentBreef = this.$accidentEventLegend[u];
        this.$urgentMissionList = this.$accidentCargoSheet[u];
        var l = Math.floor(Math.random() * 10);
        this.$urgentCargoTag = this.$urgentMissionList[l];
        var t = this.$urgentCargoTag;
        this.$urgentCargoItem = this.$accidentCargoList[t];
        this.$urgentCargoLegend = this.$accidentCargoLegend[t];
        this.$deliveryItem = this.$urgentCargoItem;
    }
    if (u >= 0) {
        deliveryInfo = expandMissionText("insystem_urgent_delivery", {
            item: this.$urgentCargoItem,
            brief: this.$urgentBreef,
            legend: this.$urgentCargoLegend,
            port: this.$destinationPort,
            insurance: this.$insuranceFee,
            premium: this.$urgentPremium.toFixed(0)
        });
        // *** case 1 - ship is not equipped for mission
        if ((player.ship.cargoSpaceAvailable < 5) || (player.credits < this.$insuranceFee)) {
            deliveryInfo = this.$missionRefuse;
            mission.runScreen({
                title: expandMissionText("insystem_screen_title"),
                background: { name: "specialCargoLoad.png", height: 480 },
                message: deliveryInfo,
            });
        }
        // *** case 2 - mission available
        else {
            mission.runScreen({
                title: expandMissionText("insystem_screen_title"),
                background: { name: "specialCargoLoad.png", height: 480 },
                message: deliveryInfo,
                choicesKey: "cargo_contract"
            },
                function (choice) {
                    if (choice === "1_Accept_Contract") this.$cargoLoad();
                }
            );
        }
    }

    // special missions
    if (u >= 0) return; // check for emergency missions priority!
    s = this.$specialMissionFlag;
    this.$deliveryItem = this.$specialCargoList[s];
    if (s >= 0) {
        deliveryInfo = expandMissionText("insystem_special_delivery", {
            item:this.$specialCargoList[s],
            legend:this.$specialCargoLegend[s],
            port:this.$destinationPort,
            insurance:this.$insuranceFee,
            premium:this.$specialPremium.toFixed(0)
        });
    }

    // *** case 1 - ship is not equipped for mission
    if ((player.ship.cargoSpaceAvailable < 5) || (player.credits < this.$insuranceFee)) {
        deliveryInfo = this.$missionRefuse;
        mission.runScreen({
            title: expandMissionText("insystem_screen_title"),
            background: { name: "specialCargoLoad.png", height: 480 },
            message: deliveryInfo,
        });
    }
    else {
        if (s >= 0) { // *** case 2 - mission available
            mission.runScreen({
                title: expandMissionText("insystem_screen_title"),
                background: { name: "specialCargoLoad.png", height: 480 },
                message: deliveryInfo,
                choicesKey: "cargo_contract"
            },
                function (choice) {
                    if (choice === "1_Accept_Contract") this.$cargoLoad();
                }
            );
        }
        else { // *** case 3 - no mission
            deliveryInfo = this.$missionEmpty;
            mission.runScreen({
                title: expandMissionText("insystem_screen_title"),
                background: { name: "specialCargoLoad.png", height: 480 },
                message: deliveryInfo,
            }
            );
        }
    }
}
