// TODO
// Add seeker episode splits by checking scene name (?GameManager, 0x40, 0x88, 0x14)
// Add crystal pick up splits by checking number of crystals held (UIManager, PickupCounter, 0x120)

state("Fe") {}

startup {
    settings.Add("perks", true, "Perks");
    settings.SetToolTip("perks", "Split at end tutorial scenes");
    settings.Add("songs", true, "Songs");
    settings.SetToolTip("songs", "Split at end voices learning");

    settings.CurrentDefaultParent = "perks";
    settings.Add("perk1", true, "Climb");
    settings.Add("perk2", true, "Glide");
    settings.Add("perk3", false, "Sprint");
    settings.Add("perk4", false, "Dash");
    settings.Add("perk5", false, "Radar");
    settings.Add("perk6", false, "Fly");

    settings.CurrentDefaultParent = "songs";
    settings.Add("song1", true, "Bird");
    settings.Add("song2", true, "Wolf");
    settings.Add("song3", true, "Lizard");
    settings.Add("song4", true, "Jelly");
    settings.Add("song5", true, "Deer");

    vars.InitVars = (Action)(() => {
        vars.initFlag = false;
        vars.perkNb = 0;
        vars.knownVoices = new bool[] {true, false, false, false, false, false};
        vars.isPlayerSeeker = 0;
    });

    vars.InitVars();

    vars.timerResetVars = (EventHandler)((s, e) => {
        vars.InitVars();
    });
    timer.OnStart += vars.timerResetVars;
}

init {
    vars.UpdatePointers = (Action)(() => {
        vars.playerAddr = new MemoryWatcher<IntPtr>((IntPtr)vars.playerManagerPtr);
        vars.playerAddr.Update(game);
        if(vars.playerAddr.Current != IntPtr.Zero) {
            print("[Autosplitter] Found : Struct " + vars.playerAddr.Current.ToString("X"));

            vars.watchers = new MemoryWatcherList() {
                vars.playerAddr,
                // PlayerCharacter
                (vars.invisible = new MemoryWatcher<byte>(vars.playerAddr.Current + 0x1CA)),
                (vars.scrollVoiceIndex = new MemoryWatcher<int> (vars.playerAddr.Current + 0x878)),
                (vars.stunned = new MemoryWatcher<byte>(vars.playerAddr.Current + 0x885)),
                (vars.isInPerkTutorial = new MemoryWatcher<byte>(vars.playerAddr.Current + 0xAA0)),
                (vars.isActivingSeekerEye = new MemoryWatcher<byte>(vars.playerAddr.Current + 0xAB3)),
                // SeekerPlayer
                (vars.inControlOfBody = new MemoryWatcher<byte>(vars.playerAddr.Current + 0x874))
            };
        } else print("[Autosplitter] Empty Address");
    });

    vars.tokenSource = new CancellationTokenSource();
    vars.token = vars.tokenSource.Token;

    vars.threadScan = new Thread(() => {
        var scanTargetPlayer = new SigScanTarget(5, "48 83 C4 20 B8 ?? ?? ?? ?? 48 89 30 B8 ?? ?? ?? ?? 48 C7 00");
        IntPtr targetPtr = IntPtr.Zero;
        while(!vars.token.IsCancellationRequested) {
            print("[Autosplitter] Scanning memory");
            foreach (var page in game.MemoryPages()) {
                var scanner = new SignatureScanner(game, page.BaseAddress, (int)page.RegionSize);
                if((targetPtr = scanner.Scan(scanTargetPlayer)) != IntPtr.Zero) {
                    print("[Autosplitter] Player Found : " + targetPtr.ToString("X"));
                    break;
                }
            }
            if (targetPtr != IntPtr.Zero) {
                vars.playerManagerPtr = game.ReadValue<int>(targetPtr);
                vars.UpdatePointers();
                print("[Autosplitter] Done scanning");
                break;
            }
            Thread.Sleep(2000);
        }
        print("[Autosplitter] Exit thread scan");
    });
    vars.threadScan.Start();
}

update {
    if(vars.threadScan.IsAlive)
        return false;

    vars.watchers.UpdateAll(game);

    if (vars.playerAddr.Changed || vars.playerAddr.Current == IntPtr.Zero)
        vars.UpdatePointers();

    // Save if is in seeker because of entity polymorphism
    if(vars.isActivingSeekerEye.Current == 1) vars.isPlayerSeeker = 1;
}

start {
    //Need to check the second time unstunned because the game unstun/stun at first display and at actual start
    if(vars.stunned.Changed && vars.stunned.Current == 0) {
        if(!vars.initFlag) {
            vars.initFlag = true;
        } else {
            vars.InitVars();
            return true;
        }
    }
}

split {
    if(vars.isPlayerSeeker == 0) {
        // Check if we went from a pseudo cinematic
        if(vars.invisible.Changed && vars.invisible.Current == 0) {
            
            // Store the number of perks because they are necessarily picked in the right order (could use unlockedPerks from player struct but unnecessarily complex)
            if (vars.isInPerkTutorial.Current == 1)
                ++vars.perkNb;

            // Voice splits : Track if we ended a voice learning by checking if a new voice is acquired/equipped
            // Store known voices because the game only have a complex/not easily accessible struct of voices so it's easier to make a new one ourselves
            if(!vars.knownVoices[vars.scrollVoiceIndex.Current]) {
                vars.knownVoices[vars.scrollVoiceIndex.Current] = true;
                return settings.ContainsKey("song"+vars.scrollVoiceIndex.Current) && settings["song"+vars.scrollVoiceIndex.Current];
            }
        }

        // Perk splits : Track if we ended a perk tutorial
        if(vars.isInPerkTutorial.Changed && vars.isInPerkTutorial.Current == 0)
            return settings["perk"+vars.perkNb];
            
    } else {
        // End split : Track if we loss the control of the seeker
        if(vars.inControlOfBody.Changed && vars.inControlOfBody.Current == 0)
            return true;
    }
}

exit {
    vars.tokenSource.Cancel();
}

shutdown {
    vars.tokenSource.Cancel();
    timer.OnStart -= vars.timerResetVars;
}