#include "pax.h"

// ZFW Entry, fill pax first (pax+bag), rest is cargo
void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, const double ZFWTarget, const bool isImperial, const bool isER) {
    // Find payload, num pax and extra cargo
    double payload = ZFWTarget - targetPayload->empty - targetPayload->pilot - targetPayload->firstOfficer - targetPayload->engineer -
                     targetPayload->cabinCrewFront - targetPayload->cabinCrewRear - targetPayload->leftAux - targetPayload->rightAux;
    unsigned short numPax = max(0.0, min((double)MAX_PAX, floor(payload / (PAX_WEIGHT(isImperial) + BAG_WEIGHT(isImperial)))));
    unsigned int cargo = round(payload - numPax * PAX_WEIGHT(isImperial) - numPax * BAG_WEIGHT(isImperial));

    distribute(targetPayload, fuel, numPax, cargo, isImperial, isER);
}

// SimBrief Entry, SB pax count and extra cargo
void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, unsigned short numPax, unsigned int cargo, const bool isImperial, const bool isER) {
    // Clear
    targetPayload->paxCount.business1 = targetPayload->paxCount.business2 = targetPayload->paxCount.economy1 = targetPayload->paxCount.economy2 =
        targetPayload->paxCount.total = 0;
    targetPayload->forwardCargo = targetPayload->rearCargo = 0;
    
    unsigned short _numPax = 0;
    unsigned int count = MAX_PAX;
    // Initial distribution pax + bags
    while (numPax > 0 && count > 0) {
        if (numPax >= 4) {
            if (targetPayload->paxCount.business1 < MAX_BUSINESS_1) {
                targetPayload->paxCount.business1++;
                _numPax++;
            }
            if (targetPayload->paxCount.business2 < MAX_BUSINESS_2) {
                targetPayload->paxCount.business2++;
                _numPax++;
            }
            if (targetPayload->paxCount.economy1 < MAX_ECONOMY_1) {
                targetPayload->paxCount.economy1++;
                _numPax++;
            }
            if (targetPayload->paxCount.economy2 < MAX_ECONOMY_2) {
                targetPayload->paxCount.economy2++;
                _numPax++;
            }
        }
        else if (numPax == 3) {
            if (targetPayload->paxCount.business2 < MAX_BUSINESS_2) {
                targetPayload->paxCount.business2++;
                _numPax++;
            }
            if (targetPayload->paxCount.economy1 < MAX_ECONOMY_1) {
                targetPayload->paxCount.economy1++;
                _numPax++;
            }
            if (targetPayload->paxCount.economy2 < MAX_ECONOMY_2) {
                targetPayload->paxCount.economy2++;
                _numPax++;
            }
        }
        else if (numPax == 2) {
            if (targetPayload->paxCount.economy1 < MAX_ECONOMY_1) {
                targetPayload->paxCount.economy1++;
                _numPax++;
            }
            if (targetPayload->paxCount.economy2 < MAX_ECONOMY_2) {
                targetPayload->paxCount.economy2++;
                _numPax++;
            }
        }
        else if (numPax == 1) {
            if (targetPayload->paxCount.economy2 < MAX_ECONOMY_2) {
                targetPayload->paxCount.economy2++;
                _numPax++;
            }
        }
        numPax -= _numPax;
        targetPayload->paxCount.total += _numPax;

        if (_numPax % 2 == 0) {
            targetPayload->forwardCargo += (_numPax / 2) * BAG_WEIGHT(isImperial);
            targetPayload->rearCargo += (_numPax / 2) * BAG_WEIGHT(isImperial);
        }
        else {
            _numPax--;
            targetPayload->forwardCargo += (_numPax / 2 + 1) * BAG_WEIGHT(isImperial);
            targetPayload->rearCargo += (_numPax / 2) * BAG_WEIGHT(isImperial);
        }

        _numPax = 0;

        count--;
    }
    count = MAX_FRONT_CARGO(isImperial);
    // Initial distibution of remaining cargo
    while (cargo > 0 && count > 0) {
        if (targetPayload->forwardCargo < MAX_FRONT_CARGO(isImperial)) {
            targetPayload->forwardCargo++;
            cargo--;
        }
        if (targetPayload->rearCargo < MAX_REAR_CARGO(isImperial, isER) && cargo > 0) {
            targetPayload->rearCargo++;
            cargo--;
        }

        count--;
    }
    // Refinement pax
    count = MAX_PAX;
    while (count > 0) {
        generatePayload(targetPayload, isImperial);
        calculateCGs(targetPayload, fuel, &targetPayload->ZFWCG, &targetPayload->TOCG, isImperial);

        // in front of target
        if (targetPayload->ZFWCG < targetPayload->CGTarget - CG_TOLERANCE) {
            if (targetPayload->paxCount.business1 > 0) {
                targetPayload->paxCount.business1--;
            }
            else if (targetPayload->paxCount.business2 > 0) {
                targetPayload->paxCount.business2--;
            }
            else if (targetPayload->paxCount.economy1 > 0) {
                targetPayload->paxCount.economy1--;
            }
            else {
                break;
            }

            if (targetPayload->paxCount.economy2 < MAX_ECONOMY_2) {
                targetPayload->paxCount.economy2++;
            }
            else if (targetPayload->paxCount.economy1 < MAX_ECONOMY_1) {
                targetPayload->paxCount.economy1++;
            }
            else if (targetPayload->paxCount.business2 < MAX_BUSINESS_2) {
                targetPayload->paxCount.business2++;
            }
            else {
                targetPayload->paxCount.business1++;
            }
        }
        // behind target
        else if (targetPayload->ZFWCG > targetPayload->CGTarget + CG_TOLERANCE) {
            if (targetPayload->paxCount.economy2 > 0) {
                targetPayload->paxCount.economy2--;
            }
            else if (targetPayload->paxCount.economy1 > 0) {
                targetPayload->paxCount.economy1--;
            }
            else if (targetPayload->paxCount.business2 > 0) {
                targetPayload->paxCount.business2--;
            }
            else {
                break;
            }

            if (targetPayload->paxCount.business1 < MAX_BUSINESS_1) {
                targetPayload->paxCount.business1++;
            }
            else if (targetPayload->paxCount.business2 < MAX_BUSINESS_2) {
                targetPayload->paxCount.business2++;
            }
            else if (targetPayload->paxCount.economy1 < MAX_ECONOMY_1) {
                targetPayload->paxCount.economy1++;
            }
            else {
                targetPayload->paxCount.economy2++;
            }
        }
        else {
            break;
        }

        count--;
    }
    // Refinement cargo
    count = MAX_FRONT_CARGO(isImperial) + MAX_REAR_CARGO(isImperial, isER);
    while (count > 0) {
        generatePayload(targetPayload, isImperial);
        calculateCGs(targetPayload, fuel, &targetPayload->ZFWCG, &targetPayload->TOCG, isImperial);

        // in front of target
        if (targetPayload->ZFWCG < targetPayload->CGTarget - CG_TOLERANCE) {
            if (targetPayload->forwardCargo > 0 && targetPayload->rearCargo < MAX_REAR_CARGO(isImperial, isER)) {
                if (targetPayload->forwardCargo > BAG_WEIGHT(isImperial) &&
                    targetPayload->rearCargo < MAX_FRONT_CARGO(isImperial) - BAG_WEIGHT(isImperial)) {
                    targetPayload->forwardCargo -= BAG_WEIGHT(isImperial);
                    targetPayload->rearCargo += BAG_WEIGHT(isImperial);
                }
                else {
                    targetPayload->forwardCargo--;
                    targetPayload->rearCargo++;
                }
            }
            else {
                break;
            }
        }
        // behind target
        else if (targetPayload->ZFWCG > targetPayload->CGTarget + CG_TOLERANCE) {
            if (targetPayload->rearCargo > 0 && targetPayload->forwardCargo < MAX_FRONT_CARGO(isImperial)) {
                if (targetPayload->rearCargo > BAG_WEIGHT(isImperial) &&
                    targetPayload->forwardCargo < MAX_REAR_CARGO(isImperial, isER) - BAG_WEIGHT(isImperial)) {
                    targetPayload->rearCargo -= BAG_WEIGHT(isImperial);
                    targetPayload->forwardCargo += BAG_WEIGHT(isImperial);
                }
                else {
                    targetPayload->rearCargo--;
                    targetPayload->forwardCargo++;
                }
            }
            else {
                break;
            }
        }
        else {
            break;
        }

        count--;
    }
}

// Updates pax stations with their respective weights
// Used internally and used for Station Entry (pax only, cargo is set directly)
void generatePayload(paxPayloadData_t* const targetPayload, const bool isImperial) {
    targetPayload->business1Left = targetPayload->business1Center = targetPayload->business1Right = (targetPayload->paxCount.business1 / 3.0) * PAX_WEIGHT(isImperial);
    targetPayload->business2Left = targetPayload->business2Center = targetPayload->business2Right = (targetPayload->paxCount.business2 / 3.0) * PAX_WEIGHT(isImperial);
    targetPayload->economy1Left = targetPayload->economy1Center = targetPayload->economy1Right = (targetPayload->paxCount.economy1 / 3.0) * PAX_WEIGHT(isImperial);
    targetPayload->economy2Left = targetPayload->economy2Center = targetPayload->economy2Right = (targetPayload->paxCount.economy2 / 3.0) * PAX_WEIGHT(isImperial);
    targetPayload->total = targetPayload->empty + targetPayload->pilot + targetPayload->firstOfficer + targetPayload->engineer + targetPayload->cabinCrewFront +
                           targetPayload->business1Left + targetPayload->business1Center + targetPayload->business1Right + targetPayload->business2Left +
                           targetPayload->business2Center + targetPayload->business2Right + targetPayload->economy1Left + targetPayload->economy1Center +
                           targetPayload->economy1Right + targetPayload->economy2Left + targetPayload->economy2Center + targetPayload->economy2Right +
                           targetPayload->cabinCrewRear + targetPayload->forwardCargo + targetPayload->rearCargo + targetPayload->leftAux + targetPayload->rightAux;
}

// Normalise to Pounds
// MANDATORY BEFORE SETTING WEIGHTS
// USE ON COPY OF GLOBAL STATE ONLY
void normalisePayload(paxPayloadData_t* const targetPayload, const bool isImperial) {
    targetPayload->empty = TO_POUNDS(isImperial, targetPayload->empty);
    targetPayload->pilot = TO_POUNDS(isImperial, targetPayload->pilot);
    targetPayload->firstOfficer = TO_POUNDS(isImperial, targetPayload->firstOfficer);
    targetPayload->engineer = TO_POUNDS(isImperial, targetPayload->engineer);
    targetPayload->cabinCrewFront = TO_POUNDS(isImperial, targetPayload->cabinCrewFront);
    targetPayload->business1Left = TO_POUNDS(isImperial, targetPayload->business1Left);
    targetPayload->business1Center = TO_POUNDS(isImperial, targetPayload->business1Center);
    targetPayload->business1Right = TO_POUNDS(isImperial, targetPayload->business1Right);
    targetPayload->business2Left = TO_POUNDS(isImperial, targetPayload->business2Left);
    targetPayload->business2Center = TO_POUNDS(isImperial, targetPayload->business2Center);
    targetPayload->business2Right = TO_POUNDS(isImperial, targetPayload->business2Right);
    targetPayload->economy1Left = TO_POUNDS(isImperial, targetPayload->economy1Left);
    targetPayload->economy1Center = TO_POUNDS(isImperial, targetPayload->economy1Center);
    targetPayload->economy1Right = TO_POUNDS(isImperial, targetPayload->economy1Right);
    targetPayload->economy2Left = TO_POUNDS(isImperial, targetPayload->economy2Left);
    targetPayload->economy2Center = TO_POUNDS(isImperial, targetPayload->economy2Center);
    targetPayload->economy2Right = TO_POUNDS(isImperial, targetPayload->economy2Right);
    targetPayload->cabinCrewRear = TO_POUNDS(isImperial, targetPayload->cabinCrewRear);
    targetPayload->forwardCargo = TO_POUNDS(isImperial, targetPayload->forwardCargo);
    targetPayload->rearCargo = TO_POUNDS(isImperial, targetPayload->rearCargo);
    targetPayload->leftAux = TO_POUNDS(isImperial, targetPayload->leftAux);
    targetPayload->rightAux = TO_POUNDS(isImperial, targetPayload->rightAux);
}

void calculateCGs(const paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, double* const ZFWCG, double* const TOCG, const bool isImperial) {
    paxPayloadData_t localPayload = {};
    memcpy(&localPayload, targetPayload, sizeof(localPayload));
    normalisePayload(&localPayload, isImperial);

    double totalMoment = localPayload.empty * ARM_EMPTY + localPayload.pilot * ARM_PILOT + localPayload.firstOfficer * ARM_FIRST_OFFICER +
                         localPayload.engineer * ARM_ENGINEER + localPayload.cabinCrewFront * ARM_PAX_CABIN_CREW_FRONT +
                         localPayload.business1Left * ARM_PAX_BUSINESS1_LEFT + localPayload.business1Center * ARM_PAX_BUSINESS1_CENTER +
                         localPayload.business1Right * ARM_PAX_BUSINESS1_RIGHT + localPayload.business2Left * ARM_PAX_BUSINESS2_LEFT +
                         localPayload.business2Center * ARM_PAX_BUSINESS2_CENTER + localPayload.business2Right * ARM_PAX_BUSINESS2_RIGHT +
                         localPayload.economy1Left * ARM_PAX_ECONOMY1_LEFT + localPayload.economy1Center * ARM_PAX_ECONOMY1_CENTER +
                         localPayload.economy1Right * ARM_PAX_ECONOMY1_RIGHT + localPayload.economy2Left * ARM_PAX_ECONOMY2_LEFT +
                         localPayload.economy2Center * ARM_PAX_ECONOMY2_CENTER + localPayload.economy2Right * ARM_PAX_ECONOMY2_RIGHT +
                         localPayload.cabinCrewRear * ARM_PAX_CABIN_CREW_REAR + localPayload.forwardCargo * ARM_FORWARD_CARGO +
                         localPayload.rearCargo * ARM_REAR_CARGO + localPayload.leftAux * ARM_LEFT_AUX + localPayload.rightAux * ARM_RIGHT_AUX;

    double totalWeight = localPayload.empty + localPayload.pilot + localPayload.firstOfficer + localPayload.engineer + localPayload.cabinCrewFront + 
                         localPayload.business1Left + localPayload.business1Center + localPayload.business1Right + localPayload.business2Left +
                         localPayload.business2Center + localPayload.business2Right + localPayload.economy1Left + localPayload.economy1Center +
                         localPayload.economy1Right + localPayload.economy2Left + localPayload.economy2Center + localPayload.economy2Right +
                         localPayload.cabinCrewRear + localPayload.forwardCargo + localPayload.rearCargo + localPayload.leftAux + localPayload.rightAux;

    *ZFWCG = TO_PERCENT_MAC(totalMoment / totalWeight);

    totalMoment += fuel->main1 * ARM_MAIN1 + fuel->main3 * ARM_MAIN3 + fuel->main2 * ARM_MAIN2 + fuel->upperAux * ARM_UPPER_AUX +
                   fuel->lowerAux * ARM_LOWER_AUX + fuel->main1Tip * ARM_MAIN1_TIP + fuel->main3Tip * ARM_MAIN3_TIP +
                   fuel->tail * ARM_TAIL + fuel->forwardAux1 * ARM_FORWARD_AUX1 + fuel->forwardAux2 * ARM_FORWARD_AUX2;

    totalWeight += fuel->total;

    *TOCG = TO_PERCENT_MAC(totalMoment / totalWeight);
}

void load(const paxPayloadData_t* const targetPayload, const HANDLE simConnect, const bool isImperial) {
    paxPayloadData_t localPayload = {};
    memcpy(&localPayload, targetPayload, sizeof(localPayload));
    normalisePayload(&localPayload, isImperial);

    SimConnect_SetDataOnSimObject(simConnect, DATA_DEFINITION_PAYLOAD_PAX, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(paxPayloadDataSet_t), &localPayload);
}

void unload(const HANDLE simConnect, const bool isER) {
    paxPayloadDataSet_t localPayload = {};

    localPayload.cabinCrewFront = FRONT_CREW_WEIGHT(true);
    localPayload.cabinCrewRear = REAR_CREW_WEIGHT(true);
    localPayload.leftAux = localPayload.rightAux = isER ? AUX_WEIGHT(true) : 0;
    localPayload.pilot = localPayload.firstOfficer = localPayload.engineer = PILOT_WEIGHT(true);

    SimConnect_SetDataOnSimObject(simConnect, DATA_DEFINITION_PAYLOAD_PAX, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(paxPayloadDataSet_t), &localPayload);
}
