

#Include "MathLib" as ML
#Include "TextLib" as TL

#Const C_PageUID "ListUids"
// default to send back to angelscript
#Const C_DefaultNbMapsMax 25

// GetUidSlice stuff
#Const C_UidChunksPerEvent 5
#Const C_MaxEventStringSize 1000
#Const C_UidInStrSize 28
#Const C_UidsPerChunk 7
#Const C_MaxUidsPerEvent 35

// Globals
declare Integer G_NextNbMapsMax;
declare Boolean G_RequestActive;
declare Ident G_LastMapIdent;
declare Integer G_MapIxAtLoad;

Void MLHookLog(Text _Msg) {
    SendCustomEvent("MLHook_LogMe_"^C_PageUID, [_Msg]);
}

// MARK: Request status

Void MarkRequestActive(Boolean _Active) {
    G_RequestActive = _Active;
    SendCustomEvent("MLHook_Event_" ^ C_PageUID ^ "_IsReqActive", [""^_Active]);
}

Void SyncReqInProgress() {
    G_RequestActive = Playground.MapList_IsInProgress;
    SendCustomEvent("MLHook_Event_" ^ C_PageUID ^ "_IsReqActive", [""^G_RequestActive]);
}

// MARK: MapList handling

Void CheckIfMapListDone() {
    if (!G_RequestActive) {
        MLHookLog("CheckIfMapListDone: Request is not active, skipping.");
        return;
    }
    if (Playground.MapList_IsInProgress) {
        // we expect this to be true while waiting.
        // MLHookLog("CheckIfMapListDone: MapList_IsInProgress, returning...");
        return;
    }
    MarkRequestActive(False);
    SendCustomEvent("MLHook_Event_" ^ C_PageUID ^ "_Clear", []);
    declare Text[] Uids;
    declare Text[] MapNames;

    declare Integer MapIx;
    // always return this slice; the user can get more if they want. Also avoids a possibly expensive .exists / .keyof
    MapIx = G_MapIxAtLoad;
    // declare Text CurrUid = Playground.Map.MapInfo.MapUid;
    // if (Playground.MapList_MapUids.exists(CurrUid)) {
    //     MapIx = Playground.MapList_MapUids.keyof(CurrUid);
    // } else {
    //     MapIx = -1;
    // }

    declare Integer StartIx = 1 + MapIx;
    declare Integer NbMaps = Playground.MapList_MapUids.count;
    // declare Integer EndIx = StartIx + ML::Min(25, NbMaps);
    declare Integer EndIx = StartIx + ML::Min(G_NextNbMapsMax, NbMaps);
    // reset after use
    G_NextNbMapsMax = C_DefaultNbMapsMax;
    if (NbMaps == 0) {
        return;
    }
    // if (NbMaps <= 10) {
    //     StartIx = 0;
    //     EndIx = NbMaps;
    // }

    declare Integer Count = 0;
    for (I, StartIx, EndIx) {
        Uids.add(Playground.MapList_MapUids[I % NbMaps]);
        MapNames.add(Playground.MapList_Names[I % NbMaps]);
        Count += 1;
        if (Count >= NbMaps) {
            break;
        }
    }

    SendCustomEvent("MLHook_Event_" ^ C_PageUID ^ "_SliceInfo", [""^StartIx, ""^EndIx, ""^NbMaps]);
    for (I, 0, Uids.count - 1) {
        // MLHookLog("MapUid: " ^ Uids[I] ^ " - MapName: " ^ MapNames[I]);
        SendCustomEvent("MLHook_Event_" ^ C_PageUID ^ "_Pair", [Uids[I], MapNames[I]]);
    }
}

Void StartUpdateUids() {
    declare Boolean WasMapListInProg = Playground.MapList_IsInProgress;
    if (WasMapListInProg) {
        MLHookLog("MapList_IsInProgress is true, waiting...");
    }
    while (Playground.MapList_IsInProgress) {
        yield;
    }
    if (WasMapListInProg) {
        MLHookLog("MapList_IsInProgress is now false, starting request.");
    }
    // else {
    //     MLHookLog("Calling MapList_Request().");
    // }
    // foreach (MapUid in Playground.MapList_MapUids) {
    //     MLHookLog("Prior MapUid: "^MapUid);
    // }
    Playground.MapList_Request();
    MarkRequestActive(True);
}

Void OnMapChange() {
    yield;
    G_LastMapIdent = Playground.Map.Id;
    MLHookLog("Map change detected: " ^ G_LastMapIdent);
    G_MapIxAtLoad = -1;
    MarkRequestActive(Playground.MapList_IsInProgress);
    if (!Playground.MapList_IsInProgress) {
        Playground.MapList_Request();
    }
    while (Playground.MapList_IsInProgress) {
        yield;
    }
    if (Playground.MapList_MapUids.exists(Playground.Map.MapInfo.MapUid)) {
        G_MapIxAtLoad = Playground.MapList_MapUids.keyof(Playground.Map.MapInfo.MapUid);
    }
    SendCustomEvent("MLHook_Event_" ^ C_PageUID ^ "_OrigMapIx", [""^G_MapIxAtLoad, Playground.Map.MapInfo.MapUid]);
}


Void SendUids(Text _Id, Integer _Start, Integer _AskCount) {
    declare Text EvtType = "MLHook_Event_" ^ C_PageUID ^ "_UidsResp";
    declare Integer NbMaps = Playground.MapList_MapUids.count;
    declare Integer ResponseCount = ML::Min(_AskCount, NbMaps);
    declare Integer NbEvents = 1 + ((ResponseCount - 1) / C_MaxUidsPerEvent);

    declare Integer CountDone = 0;
    declare Integer UidsLeft = ResponseCount;
    declare Text[] EventData = [_Id];
    declare Text[] UidSlice = [];
    while (CountDone < ResponseCount) {
        declare Integer StartIx = (_Start + CountDone) % NbMaps;
        // declare Text[] UidSlice = Playground.MapList_MapUids.slice(StartIx, C_UidsPerChunk);
        UidSlice.clear();
        declare Integer UidsThisRound = ML::Min(C_UidsPerChunk, UidsLeft);
        for (I, StartIx, StartIx + UidsThisRound - 1) {
            UidSlice.add(Playground.MapList_MapUids[I % NbMaps]);
        }
        EventData.add(TL::Join(",", UidSlice));
        if (EventData.count >= 6) {
            SendCustomEvent(EvtType, EventData);
            EventData.clear();
            EventData.add(_Id);
        }
        CountDone += UidsThisRound;
        UidsLeft -= UidsThisRound;
    }

    if (EventData.count > 1) {
        SendCustomEvent(EvtType, EventData);
    }
}

// MARK: Incoming events

Boolean CheckIncomingMsgs() {
    declare Text[][] MLHook_Inbound_ListUids for ClientUI = [];
    declare Text[][] InboundEvents = MLHook_Inbound_ListUids;
    MLHook_Inbound_ListUids = [];

    foreach (Event in InboundEvents) {
        switch (Event.count) {
            case 0: {
                // Request
                StartUpdateUids();
                break;
            }
            case 1: {
                // Request with larger max
                G_NextNbMapsMax = ML::Max(TL::ToInteger(Event[0]), C_DefaultNbMapsMax);
                StartUpdateUids();
                break;
            }
            case 3: {
                // Get uids slice (id, start, end)
                SendUids(Event[0], TL::ToInteger(Event[1]), TL::ToInteger(Event[2]));
                break;
            }
            default: {
                MLHookLog("Skipped unknown incoming event: " ^ Event);
                continue;
            }
        }
        // MLHookLog("Processed Incoming Event: "^Event);
    }
    return InboundEvents.count > 0;
}

main() {
    G_NextNbMapsMax = C_DefaultNbMapsMax;
    G_MapIxAtLoad = -1;
    yield;
    declare Integer Count = 0;
    while (True) {
        Count += 1;
        if (Playground.Map == Null) {
            yield;
            continue;
        }
        if (G_LastMapIdent != Playground.Map.Id) {
            OnMapChange();
        }
        if (G_RequestActive) {
            CheckIfMapListDone();
        }
        if (Count % 120 == 0) {
            SyncReqInProgress();
        }
        CheckIncomingMsgs();
        yield;
    }
}
