// SPDX-FileCopyrightText: 2024 Kapdap <kapdap@pm.me>
//
// SPDX-License-Identifier: MIT
/*  Die Hard Trilogy (PS1) Auto Splitter
 *  https://github.com/kapdap/die-hard-trilogy-ps1-autosplitter
 *
 *  Copyright 2024 Kapdap <kapdap@pm.me>
 *
 *  Use of this source code is governed by an MIT-style
 *  license that can be found in the LICENSE file or at
 *  https://opensource.org/licenses/MIT.
 */
state("LiveSplit") {}

startup
{
    Assembly.Load(File.ReadAllBytes("Components/emu-help-v2")).CreateInstance("PS1");

    vars.Helper.Load = (Func<dynamic, bool>)(emu =>
    {
        emu.MakeString("US", 11, 0x800094B4); // SLUS_001.19
        emu.MakeString("EU", 11, 0x8000949C); // SLES_004.45
        emu.MakeString("JP", 11, 0x800094CC); // SLPS_005.85

        emu.Make<short>("Game", 0x80012800); // 0:Loader, 1:Menu, 2:DH1, 3:DH2, 4:DH3

        // NTSC-U - Die Hard 1
        emu.Make<short>("US_DH1GameState",  0x801CB094); // 2:Menu, 3:Menu, 4:Demo, 5:Game
        emu.Make<short>("US_DH1GameEnded",  0x800A346C); // 4:Level Completion, 5:Game Completion
        emu.Make<short>("US_DH1Level",      0x800A33F4); // 23:Last Level
        emu.Make<int>  ("US_DH1LevelTime",  0x800A33DC);

        // NTSC-U - Die Hard 2
        emu.Make<short>("US_DH2GameState",  0x800B6AA0); // 2:Menu, 3:Game, 5:Demo
        emu.Make<short>("US_DH2Level",      0x800B7220); // 7:Last Level
        emu.Make<int>  ("US_DH2GameTime",   0x800B6D08);
        emu.Make<int>  ("US_DH2LevelTime",  0x800B6CC8);

        // NTSC-U - Die Hard 3
        emu.Make<short>("US_DH3GameState",  0x800B5D0C); // 2:Menu, 3:Game, 5:Demo, 8:Scores
        emu.Make<short>("US_DH3Level",      0x800B64B4); // 15:Last Level
        emu.Make<int>  ("US_DH3LevelTime",  0x800B56A4);
        emu.Make<int>  ("US_DH3LevelTimeB", 0x801DA704);
        emu.Make<short>("US_DH3Phone",      0x800B650C);

        // PAL - Die Hard 1
        emu.Make<short>("EU_DH1GameState",  0x801CE928);
        emu.Make<short>("EU_DH1GameEnded",  0x800A6D7C);
        emu.Make<short>("EU_DH1Level",      0x800A6D3C);
        emu.Make<int>  ("EU_DH1LevelTime",  0x800A6D30);

        // PAL - Die Hard 2
        emu.Make<short>("EU_DH2GameState",  0x800B9398);
        emu.Make<short>("EU_DH2Level",      0x800B9B20);
        emu.Make<int>  ("EU_DH2GameTime",   0x800B9608);
        emu.Make<int>  ("EU_DH2LevelTime",  0x800B8C7C);

        // PAL - Die Hard 3
        emu.Make<short>("EU_DH3GameState",  0x800B7F0C);
        emu.Make<short>("EU_DH3Level",      0x800B86BC);
        emu.Make<int>  ("EU_DH3LevelTime",  0x800B7888);
        emu.Make<int>  ("EU_DH3LevelTimeB", 0x801DC914);
        emu.Make<short>("EU_DH3Phone",      0x800B8714);

        // NTSC-J - Die Hard 1
        emu.Make<short>("JP_DH1GameState",  0x801CC8B8);
        emu.Make<short>("JP_DH1GameEnded",  0x800A4D0C);
        emu.Make<short>("JP_DH1Level",      0x800A4CCC);
        emu.Make<int>  ("JP_DH1LevelTime",  0x800A4CC0);

        // NTSC-J - Die Hard 2
        emu.Make<short>("JP_DH2GameState",  0x800B6B8C);
        emu.Make<short>("JP_DH2Level",      0x800B730C);
        emu.Make<int>  ("JP_DH2GameTime",   0x800B6E04);
        emu.Make<int>  ("JP_DH2LevelTime",  0x800B6DC4);

        // NTSC-J - Die Hard 3
        emu.Make<short>("JP_DH3GameState",  0x800B8900);
        emu.Make<short>("JP_DH3Level",      0x800B90B0);
        emu.Make<int>  ("JP_DH3LevelTime",  0x800B8B90);
        emu.Make<int>  ("JP_DH3LevelTimeB", 0x801DD308);
        emu.Make<short>("JP_DH3Phone",      0x800B9108);

        return true;
    });

    settings.Add("dh1", true, "Die Hard 1");
    settings.Add("dh1start", true, "Auto Start", "dh1");
    settings.SetToolTip("dh1start", "Automatically starts the timer when a new game has started");
    settings.Add("dh1levels", true, "Level Splits", "dh1");
    settings.SetToolTip("dh1levels", "Splits at the start of each level");
    settings.Add("dh1bonus", false, "Bonus Splits", "dh1");
    settings.SetToolTip("dh1bonus", "Enable spliting on bonus levels");
    settings.Add("dh1endgame", false, "Ending Split", "dh1");
    settings.SetToolTip("dh1endgame", "Splits when the screen fades to black after the final level");

    settings.Add("dh2", true, "Die Hard 2");
    settings.Add("dh2start", true, "Auto Start", "dh2");
    settings.SetToolTip("dh2start", "Automatically starts the timer when a new game has started");
    settings.Add("dh2levels", true, "Level Splits", "dh2");
    settings.SetToolTip("dh2levels", "Splits at the start of each level");
    settings.Add("dh2endgame", false, "Ending Split", "dh2");
    settings.SetToolTip("dh2endgame", "Splits when the screen fades to black after the final level");

    settings.Add("dh3", true, "Die Hard 3");
    settings.Add("dh3start", true, "Auto Start", "dh3");
    settings.SetToolTip("dh3start", "Automatically starts the timer when a new game has started");
    settings.Add("dh3levels", true, "Level Splits", "dh3");
    settings.SetToolTip("dh3levels", "Splits at the start of each level");
    settings.Add("dh3phones", false, "Bomb Splits", "dh3");
    settings.SetToolTip("dh3phones", "Splits after detonating each bomb");
    settings.Add("dh3endgame", false, "Ending Split", "dh3");
    settings.SetToolTip("dh3endgame", "Splits when the screen fades to black after the final level");

    settings.Add("options", false, "Options");
    settings.Add("IGT", false, "Enable In Game Time", "options");
    settings.SetToolTip("IGT", "Allows In Game Time to be displayed and tracked");

    settings.Add("infogroup", false, "Info");
    settings.Add("infogroup1", false, "Die Hard Trilogy (PS1) Auto Splitter by Kapdap", "infogroup");
    settings.Add("infogroup2", false, "Website: https://github.com/kapdap/die-hard-trilogy-ps1-autosplitter", "infogroup");
}

init
{
    vars.StartTime = 0;
    vars.AccumulatedTime = 0;
    vars.GameStart = false;
    vars.GameEnded = false;
    vars.GameRegion = string.Empty;
    vars.UpdateRegion = (Action) (() =>
    {
        if (current.US == "SLUS_001.19")
            vars.GameRegion = "US";
        else if (current.EU == "SLES_004.45")
            vars.GameRegion = "EU";
        else if (current.JP == "SLPS_005.85")
            vars.GameRegion = "JP";
        else
            vars.GameRegion = string.Empty;
    });

    vars.Read = (Func<string, dynamic>)(name => vars.Helper[vars.GameRegion + "_" + name]);
}

start
{
    vars.UpdateRegion();

    if (vars.GameRegion == string.Empty)
        return false;

    if (current.Game == 2)
    {
        return vars.GameStart = settings["dh1start"] &&
               vars.Read("DH1GameState").Current == 5 &&
               vars.Read("DH1LevelTime").Current >= 0 &&
               vars.Read("DH1LevelTime").Current <= 15 &&
               vars.Read("DH1Level").Current == 0;
    }
    else if (current.Game == 3)
    {
        if (settings["dh2start"] &&
            vars.Read("DH2GameState").Current == 3 &&
            vars.Read("DH2LevelTime").Current >= 0 &&
            vars.Read("DH2LevelTime").Current <= 15 &&
            vars.Read("DH2Level").Current == 0)
        {
            vars.StartTime = vars.Read("DH2GameTime").Current;
            return vars.GameStart = true;
        }
    }
    else if (current.Game == 4)
    {
        return vars.GameStart = settings["dh3start"] &&
               vars.Read("DH3GameState").Current == 3 &&
               vars.Read("DH3LevelTime").Current >= 0 &&
               vars.Read("DH3LevelTime").Current <= 15 &&
               vars.Read("DH3LevelTimeB").Current != -1 &&
               vars.Read("DH3Level").Current == 0;
    }

    return false;
}

update
{
    vars.UpdateRegion();

    if (vars.GameRegion == string.Empty)
        return false;

    int time = 0;

    if (current.Game == 3)
        time = vars.Read("DH2GameTime").Current - vars.StartTime;
    else if (current.Game == 2 || current.Game == 4)
    {
        int tcur = vars.Read("DH1LevelTime").Current;
        int told = vars.Read("DH1LevelTime").Old;

        if (current.Game == 4)
        {
            tcur = vars.Read("DH3LevelTime").Current;
            told = vars.Read("DH3LevelTime").Old;
        }

        if (tcur < told && vars.GameStart)
            vars.AccumulatedTime += told;

        time = vars.AccumulatedTime + tcur;
    }

    current.GameTime = TimeSpan.FromSeconds(time / (vars.GameRegion == "EU" ? 50.0 : 60.0));
}

split
{
    if (current.Game == 2)
    {
        var level = vars.Read("DH1Level");
        
        if (!settings["dh1bonus"] && level.Current != level.Old &&
           (level.Current == 3  || level.Current == 8  ||
            level.Current == 13 || level.Current == 18 || 
            level.Current == 22))
            return false;
            
        if (settings["dh1levels"] && level.Current != level.Old)
            return true;

        if (settings["dh1endgame"] && !vars.GameEnded &&
            vars.Read("DH1GameState").Current == 5 &&
            vars.Read("DH1GameEnded").Current == 5 &&
            level.Current == 23)
            return vars.GameEnded = true;
    }
    else if (current.Game == 3)
    {
        var level = vars.Read("DH2Level");

        if (settings["dh2levels"] && level.Current != level.Old)
            return true;

        if (settings["dh2endgame"] && !vars.GameEnded &&
            vars.Read("DH2GameState").Current == 2 &&
            level.Current == 7)
            return vars.GameEnded = true;
    }
    else if (current.Game == 4)
    {
        var phone = vars.Read("DH3Phone");
        var level = vars.Read("DH3Level");

        if (settings["dh3phones"] &&
            phone.Current != phone.Old &&
            phone.Current != 0 &&
            phone.Current != 1)
            return true;

        if (settings["dh3levels"] && level.Current != level.Old)
            return true;

        if (settings["dh3endgame"] && !vars.GameEnded &&
            vars.Read("DH3GameState").Current == 2 &&
            level.Current == 15)
            return vars.GameEnded = true;
    }

    return false;
}

gameTime
{
    if (settings["IGT"])
        return current.GameTime;
}

isLoading
{
    return settings["IGT"];
}

onReset
{
    vars.AccumulatedTime = 0;
    vars.StartTime = 0;
    vars.GameStart = false;
    vars.GameEnded = false;
}