//============================================================
// Combined Glow Script for Sven-Coop with Persistent Glow
// 
//   • Chat commands: !glow, !glow intensity <min> <max>, !glow1 to !glow11, and !noglow
//   • Glow effects with short names:
//       1. Sharp       - Sharp Transition
//       2. Pulse       - Dynamic Pulse (Breathing)
//       3. Bands       - Stepped Color Bands
//       4. Rainbow     - Palette Cycling (Smooth Rainbow)
//       5. Flicker     - Random Flicker
//       6. Hue Shift   - Improved HSV-based Hue Rotation
//       7. Radial      - Radial Wave Gradient
//       8. Heatmap     - Toned Down Heatmap Gradient
//       9. Glitch      - Data Corruption Glitch Effect
//      10. Chromatic   - Chromatic Aberration Effect
//      11. Romantic    - Romantic Gradient (Soft Pink Transitions)
//============================================================

const float PI = 3.14159265358979323846f;

array<Vector> g_palette = {
    Vector(255, 0, 0),
    Vector(255, 127, 0),
    Vector(255, 255, 0),
    Vector(0, 255, 0),
    Vector(0, 0, 255),
    Vector(75, 0, 130),
    Vector(148, 0, 211)
};

array<Vector> g_romanticPalette = {
    Vector(255, 20, 147),   // Deep Pink
    Vector(255, 105, 180),  // Hot Pink
    Vector(255, 182, 193),  // Light Pink
    Vector(219, 112, 147)   // Pale Violet Red
};

float modF(float a, float b)
{
    return a - floor(a / b) * b;
}

int RandomInt(int low, int high)
{
    return int(Math.RandomFloat(float(low), float(high)));
}

void ResetPlayerGlow(CBasePlayer@ pPlayer)
{
    if (pPlayer !is null && g_EntityFuncs.IsValidEntity(pPlayer.edict()))
    {
        pPlayer.pev.renderfx = 0;
        pPlayer.pev.rendercolor = Vector(255, 255, 255);
        pPlayer.pev.renderamt = 0;
    }
}

//------------------------------------------------------------
// Persistent Data Storage: Glow settings per player (by entity index)
// Format: "glowType,minIntensity,maxIntensity"
//------------------------------------------------------------
dictionary g_glowPersist;

//------------------------------------------------------------
// Class: GlowInfo
// Stores per-player glow information.
//------------------------------------------------------------
class GlowInfo {
    private EHandle h_player;  // Safe reference to the player.
    int intensity;
    int delta;       // +1 or -1 for intensity change.
    int glowType;    // Glow style (1 to 11).
    Vector flickerColor;
    float nextFlickerTime;
    float effectSpeed;   // For the radial gradient.
    Vector effectParams; // For further customization if needed.
    
    // *** New Fields for Intensity Range ***
    int minIntensity;
    int maxIntensity;
    
    // Returns the player entity.
    CBasePlayer@ getPlayer() {
        return cast<CBasePlayer@>(h_player.GetEntity());
    }
    
    GlowInfo(CBasePlayer@ p) {
        h_player = EHandle(p);
        intensity = 3; // Initial intensity is set to a middle value.
        delta = 1;
        glowType = 1;
        flickerColor = Vector(255, 255, 255);
        nextFlickerTime = 0.0f;
        effectSpeed = 1.0f;
        effectParams = Vector(0, 0, 0);
        // Set default intensity boundaries.
        minIntensity = 1;
        maxIntensity = 5;
    }
}

// Global array that holds active GlowInfo objects.
array<GlowInfo@> glowInfos;

//------------------------------------------------------------
// Helper: Retrieve a player's GlowInfo (if exists)
//------------------------------------------------------------
GlowInfo@ GetGlowInfoForPlayer(CBasePlayer@ pPlayer)
{
    for (uint i = 0; i < glowInfos.length(); i++)
    {
        if (glowInfos[i].getPlayer() is pPlayer)
            return glowInfos[i];
    }
    return null;
}

//------------------------------------------------------------
// Helper: Update persistent glow settings for a player.
//------------------------------------------------------------
void SetGlowTypeForPlayer(CBasePlayer@ pPlayer, int glowType)
{
    string sIndex = "" + pPlayer.entindex();
    int currentMin = 1;
    int currentMax = 5;
    string data;
    if (g_glowPersist.get(sIndex, data))
    {
        array<string> parts = data.Split(",");
        if (parts.length() >= 3)
        {
            currentMin = atoi(parts[1]);
            currentMax = atoi(parts[2]);
        }
    }
    // Update persistent data with the new glow type.
    g_glowPersist.set(sIndex, "" + glowType + "," + currentMin + "," + currentMax);
}

//------------------------------------------------------------
// StartModelGlow: Begin or update a player's glow effect.
//------------------------------------------------------------
void StartModelGlow(CBasePlayer@ pPlayer, int glowType)
{
    string sIndex = "" + pPlayer.entindex();
    string data;
    int savedGlowType = glowType; // default glow type from parameter
    int savedMinIntensity = 1;
    int savedMaxIntensity = 5;
    
    // Check if persistent data exists.
    if (g_glowPersist.get(sIndex, data))
    {
        array<string> parts = data.Split(",");
        if (parts.length() >= 3)
        {
            savedGlowType = atoi(parts[0]);
            savedMinIntensity = atoi(parts[1]);
            savedMaxIntensity = atoi(parts[2]);
        }
    }
    else
    {
        // Set persistent data with default values if not present.
        g_glowPersist.set(sIndex, "" + glowType + "," + savedMinIntensity + "," + savedMaxIntensity);
    }
    
    GlowInfo@ info = GetGlowInfoForPlayer(pPlayer);
    if (info !is null)
    {
        info.glowType = savedGlowType;
        info.minIntensity = savedMinIntensity;
        info.maxIntensity = savedMaxIntensity;
        return;
    }
    GlowInfo newInfo(pPlayer);
    newInfo.glowType = savedGlowType;
    newInfo.minIntensity = savedMinIntensity;
    newInfo.maxIntensity = savedMaxIntensity;
    glowInfos.insertLast(newInfo);
}

//------------------------------------------------------------
// StopModelGlow: Remove glow from a player.
//------------------------------------------------------------
void StopModelGlow(CBasePlayer@ pPlayer, bool removePersistence = true)
{
    string sIndex = "" + pPlayer.entindex();
    if (removePersistence)
    {
        g_glowPersist.delete(sIndex);
    }
    
    for (int i = int(glowInfos.length()) - 1; i >= 0; i--)
    {
        if (glowInfos[i].getPlayer() is pPlayer)
        {
            ResetPlayerGlow(pPlayer);
            glowInfos.removeAt(i);
        }
    }
}

//------------------------------------------------------------
// Gradient Functions
//------------------------------------------------------------

// 1. Sharp Transition (Triangular Wave)
void RGBGradientSharp(float t, int& out r, int& out g, int& out b)
{
    float frequency = 0.1f;
    r = int(128.0f * abs(modF(frequency * t + 0.0f, 2.0f) - 1.0f) + 127.0f);
    g = int(128.0f * abs(modF(frequency * t + 0.6667f, 2.0f) - 1.0f) + 127.0f);
    b = int(128.0f * abs(modF(frequency * t + 1.3333f, 2.0f) - 1.0f) + 127.0f);
}

// 2. Dynamic Pulse with Dimming (Breathing)
void RGBGradientPulse(float t, int& out r, int& out g, int& out b)
{
    float baseIntensity = sin(0.5f * t) * 0.5f + 0.5f;
    float frequency = 0.1f;
    r = int((sin(frequency * t + 0.0f) * 127 + 128) * baseIntensity);
    g = int((sin(frequency * t + 2 * PI / 3) * 127 + 128) * baseIntensity);
    b = int((sin(frequency * t + 4 * PI / 3) * 127 + 128) * baseIntensity);
}

// 3. Stepped Color Bands (Red, Green, Blue)
void RGBGradientBands(float t, int& out r, int& out g, int& out b)
{
    float frequency = 0.01f;
    float phase = modF(frequency * t, 1.0f);
    if (phase < 0.33f)
    {
        r = 255; g = 0; b = 0;
    }
    else if (phase < 0.66f)
    {
        r = 0; g = 255; b = 0;
    }
    else
    {
        r = 0; g = 0; b = 255;
    }
}

// 4. Palette Cycling (Smooth Rainbow) - Uses global g_palette
void RGBGradientPalette(float t, int& out r, int& out g, int& out b)
{
    float frequency = 0.01f;
    int palSize = g_palette.length();
    float x = modF(frequency * t, 1.0f) * palSize;
    int idx1 = int(floor(x)) % palSize;
    int idx2 = (idx1 + 1) % palSize;
    float fraction = x - floor(x);
    r = int(g_palette[idx1].x * (1.0f - fraction) + g_palette[idx2].x * fraction);
    g = int(g_palette[idx1].y * (1.0f - fraction) + g_palette[idx2].y * fraction);
    b = int(g_palette[idx1].z * (1.0f - fraction) + g_palette[idx2].z * fraction);
}

// 5. Random Flicker (updates roughly every 0.5 sec)
void RGBGradientFlicker(float baseTime, int& out r, int& out g, int& out b, GlowInfo@ info)
{
    if (baseTime >= info.nextFlickerTime)
    {
        info.flickerColor.x = float(RandomInt(0, 256));
        info.flickerColor.y = float(RandomInt(0, 256));
        info.flickerColor.z = float(RandomInt(0, 256));
        info.nextFlickerTime = baseTime + 0.5f;
    }
    r = int(info.flickerColor.x);
    g = int(info.flickerColor.y);
    b = int(info.flickerColor.z);
}

// 6. Improved HSV-based Hue Rotation
void RGBGradientHue(float t, int& out r, int& out g, int& out b)
{
    float hue = modF(t * 60.0f, 360.0f);
    float saturation = 1.0f;
    float value = 1.0f;
    float c = value * saturation;
    float x = c * (1.0f - abs(modF(hue / 60.0f, 2.0f) - 1.0f));
    float m = value - c;
    float r1, g1, b1;
    if (hue < 60.0f) { r1 = c; g1 = x; b1 = 0; }
    else if (hue < 120.0f) { r1 = x; g1 = c; b1 = 0; }
    else if (hue < 180.0f) { r1 = 0; g1 = c; b1 = x; }
    else if (hue < 240.0f) { r1 = 0; g1 = x; b1 = c; }
    else if (hue < 300.0f) { r1 = x; g1 = 0; b1 = c; }
    else { r1 = c; g1 = 0; b1 = x; }
    r = int((r1 + m) * 255);
    g = int((g1 + m) * 255);
    b = int((b1 + m) * 255);
}

// 7. Radial Wave Gradient (Expanding Color Rings)
void RGBGradientRadial(float t, int& out r, int& out g, int& out b, GlowInfo@ info)
{
    float speed = 0.25f * info.effectSpeed;
    float spread = 3.0f;
    float angle = t * speed;
    float distance = (sin(angle * 2.0f) + 1.0f) * 0.5f;
    distance = 1.0f - abs(modF(distance * spread, 1.0f) - 0.5f) * 2.0f;
    r = int(255 * abs(sin(angle)) * distance);
    g = int(255 * abs(sin(angle + PI / 1.5f)) * distance);
    b = int(255 * abs(sin(angle + PI / 0.75f)) * distance);
}

// 8. Toned Down Heatmap Gradient
void RGBGradientHeatmap(float t, int& out r, int& out g, int& out b)
{
    float temp = sin(t * 0.5f) * 0.5f + 0.5f;
    if (temp < 0.5f)
    {
        float f = temp * 2.0f;
        r = int((1.0f - f) * 80 + f * 150);
        g = 80;
        b = int((1.0f - f) * 180 + f * 120);
    }
    else
    {
        float f = (temp - 0.5f) * 2.0f;
        r = int((1.0f - f) * 150 + f * 200);
        g = 80;
        b = int((1.0f - f) * 120 + f * 80);
    }
}

// 9. Data Corruption Glitch Effect
void RGBGradientGlitch(float t, int& out r, int& out g, int& out b)
{
    if (Math.RandomFloat(0.0f, 1.0f) < 0.05f)
    {
        r = RandomInt(0, 256);
        g = RandomInt(0, 256);
        b = RandomInt(0, 256);
    }
    else if (Math.RandomFloat(0.0f, 1.0f) < 0.2f)
    {
        float base = sin(t * 0.1f) * 127 + 128;
        r = int(base + RandomInt(-64, 65));
        g = int(base + RandomInt(-64, 65));
        b = int(base + RandomInt(-64, 65));
    }
    else
    {
        float frequency = 0.1f;
        r = int(sin(frequency * t + 0) * 127 + 128);
        g = int(sin(frequency * t + 2 * PI / 3) * 127 + 128);
        b = int(sin(frequency * t + 4 * PI / 3) * 127 + 128);
    }
}

// 10. Chromatic Aberration Effect
void RGBGradientChromatic(float t, int& out r, int& out g, int& out b)
{
    float frequency = 0.1f;
    float offset = PI / 4.0f;
    r = int(sin(frequency * t) * 127 + 128);
    g = int(sin(frequency * t + offset) * 127 + 128);
    b = int(sin(frequency * t + offset * 2.0f) * 127 + 128);
}

// 11. Romantic Gradient (Soft Pink Transitions)
void RGBGradientWarm(float t, int& out r, int& out g, int& out b)
{
    float frequency = 0.005f; // Slower transitions
    int palSize = g_romanticPalette.length();
    float x = modF(frequency * t, 1.0f) * palSize;
    int idx1 = int(floor(x)) % palSize;
    int idx2 = (idx1 + 1) % palSize;
    float fraction = x - floor(x);
    r = int(g_romanticPalette[idx1].x * (1.0f - fraction) + g_romanticPalette[idx2].x * fraction);
    g = int(g_romanticPalette[idx1].y * (1.0f - fraction) + g_romanticPalette[idx2].y * fraction);
    b = int(g_romanticPalette[idx1].z * (1.0f - fraction) + g_romanticPalette[idx2].z * fraction);
}

//------------------------------------------------------------
// Glow Update Loop (Scheduled Function)
//------------------------------------------------------------
void RGBModelGlow()
{
    float baseTime = g_Engine.time;
    float t = baseTime * 2.0f * 60.0f;
    int r, g, b;
    
    // Iterate backward so removals are safe.
    for (int i = int(glowInfos.length()) - 1; i >= 0; i--)
    {
        GlowInfo@ info = glowInfos[i];
        CBasePlayer@ pPlayer = info.getPlayer();
        if (pPlayer is null || !g_EntityFuncs.IsValidEntity(pPlayer.edict()) || !pPlayer.IsAlive())
        {
            if (pPlayer !is null && g_EntityFuncs.IsValidEntity(pPlayer.edict()))
                ResetPlayerGlow(pPlayer);
            glowInfos.removeAt(i);
            continue;
        }
        
        switch(info.glowType)
        {
            case 1:  RGBGradientSharp(t, r, g, b); break;
            case 2:  RGBGradientPulse(t, r, g, b); break;
            case 3:  RGBGradientBands(t, r, g, b); break;
            case 4:  RGBGradientPalette(t, r, g, b); break;
            case 5:  RGBGradientFlicker(baseTime, r, g, b, info); break;
            case 6:  RGBGradientHue(t, r, g, b); break;
            case 7:  RGBGradientRadial(t, r, g, b, info); break;
            case 8:  RGBGradientHeatmap(t, r, g, b); break;
            case 9:  RGBGradientGlitch(t, r, g, b); break;
            case 10: RGBGradientChromatic(t, r, g, b); break;
            case 11: RGBGradientWarm(t, r, g, b); break;
            default: RGBGradientSharp(t, r, g, b); break;
        }
        
        // --- Updated Intensity Oscillation using custom range ---
        info.intensity += info.delta;
        if (info.intensity >= info.maxIntensity) { 
            info.intensity = info.maxIntensity; 
            info.delta = -1; 
        } else if (info.intensity <= info.minIntensity) { 
            info.intensity = info.minIntensity; 
            info.delta = 1; 
        }
        if (Math.RandomFloat(0.0f, 1.0f) < 0.05f)
            info.delta = -info.delta;
        
        pPlayer.pev.renderfx = kRenderFxGlowShell;
        pPlayer.pev.rendercolor = Vector(r, g, b);
        pPlayer.pev.renderamt = info.intensity;
    }
}

//------------------------------------------------------------
// ReapplyGlow: Helper to reassign a player's glow from persistence.
//------------------------------------------------------------
void ReapplyGlow(CBasePlayer@ pPlayer)
{
    string sIndex = "" + pPlayer.entindex();
    string data;
    if (g_glowPersist.get(sIndex, data))
    {
        // Data expected format: "glowType,minIntensity,maxIntensity"
        array<string> parts = data.Split(",");
        if (parts.length() >= 1)
        {
            int storedGlowType = atoi(parts[0]);
            StartModelGlow(pPlayer, storedGlowType);
        }
    }
}

//------------------------------------------------------------
// Hook: ClientPutInServer
//------------------------------------------------------------
HookReturnCode ClientPutInServer(CBasePlayer@ pPlayer)
{
    ReapplyGlow(pPlayer);
    return HOOK_CONTINUE;
}

//------------------------------------------------------------
// Hook: PlayerSpawn (Reapply Glow on Respawn)
//------------------------------------------------------------
HookReturnCode PlayerSpawn(CBasePlayer@ pPlayer)
{
    if (pPlayer !is null)
        ReapplyGlow(pPlayer);
    return HOOK_CONTINUE;
}

//------------------------------------------------------------
// Chat Hook: Listen for Glow Commands
//------------------------------------------------------------
HookReturnCode ClientSay(SayParameters@ pParams)
{
    CBasePlayer@ pPlayer = pParams.GetPlayer();
    const CCommand@ args = pParams.GetArguments();

    if (args.ArgC() >= 1)
    {
        string command = args.Arg(0);

        if (command == "!noglow")
        {
            StopModelGlow(pPlayer, true);
            pParams.ShouldHide = true;
            return HOOK_CONTINUE;
        }

        if (command.StartsWith("!glow"))
        {
            if (args.ArgC() >= 2 && args.Arg(1) == "intensity")
            {
                if (args.ArgC() != 4)
                {
                    g_PlayerFuncs.ClientPrint(pPlayer, HUD_PRINTTALK, "Usage: !glow intensity <min> <max>\n");
                    pParams.ShouldHide = true;
                    return HOOK_CONTINUE;
                }

                int newMin = atoi(args.Arg(2));
                int newMax = atoi(args.Arg(3));
                if (newMin < 1 || newMax > 255 || newMin > newMax)
                {
                    g_PlayerFuncs.ClientPrint(pPlayer, HUD_PRINTTALK, "Error: Intensity values must be between 1 and 255 and min must not exceed max.\n");
                    pParams.ShouldHide = true;
                    return HOOK_CONTINUE;
                }

                GlowInfo@ info = GetGlowInfoForPlayer(pPlayer);
                if (info is null)
                {
                    // If no glow is active, start with a default glow type.
                    StartModelGlow(pPlayer, 1);
                    @info = GetGlowInfoForPlayer(pPlayer);
                }
                info.minIntensity = newMin;
                info.maxIntensity = newMax;

                // Update persistent data with new intensity values.
                string sIndex = "" + pPlayer.entindex();
                g_glowPersist.set(sIndex, "" + info.glowType + "," + newMin + "," + newMax);

                // Clamp current intensity within the new boundaries.
                if (info.intensity < info.minIntensity)
                    info.intensity = info.minIntensity;
                if (info.intensity > info.maxIntensity)
                    info.intensity = info.maxIntensity;

                g_PlayerFuncs.ClientPrint(pPlayer, HUD_PRINTTALK, "Glow intensity range set: min = " + newMin + ", max = " + newMax + "\n");

                pParams.ShouldHide = true;
                return HOOK_CONTINUE;
            }

            // For commands like !glow1, !glow2, etc., extract the number.
            string glowNumber = command.SubString(5); // Get everything after !glow.
            int glowType = atoi(glowNumber);

            if (glowType >= 1 && glowType <= 11)
            {
                SetGlowTypeForPlayer(pPlayer, glowType);
                StartModelGlow(pPlayer, glowType);
                pParams.ShouldHide = true;
                return HOOK_CONTINUE;
            }
        }

        if (command == "!glow")
        {
            g_PlayerFuncs.ClientPrint(pPlayer, HUD_PRINTTALK, "Glow commands have been posted to your console.\n");

            array<string> helpLines = {
                "Glow Commands:",
                " !glow intensity <min> <max> - Set glow intensity range (min between 1 and 255, max between 1 and 255)",
                " !glow1 - Sharp Transition",
                " !glow2 - Pulse (Breathing)",
                " !glow3 - Bands (Stepped Color Bands)",
                " !glow4 - Rainbow (Smooth Palette Cycling)",
                " !glow5 - Flicker (Random Flicker)",
                " !glow6 - Hue Shift (HSV-based Hue Rotation)",
                " !glow7 - Radial (Expanding Color Rings)",
                " !glow8 - Heatmap (Toned Down Heatmap Gradient)",
                " !glow9 - Glitch (Data Corruption Glitch Effect)",
                " !glow10 - Chromatic (Chromatic Aberration Effect)",
                " !glow11 - Romantic (Soft Pink Transitions)",
                " !noglow - Remove Glow"
            };

            for (uint i = 0; i < helpLines.length(); i++)
            {
                g_PlayerFuncs.ClientPrint(pPlayer, HUD_PRINTCONSOLE, helpLines[i] + "\n");
            }

            pParams.ShouldHide = true;
            return HOOK_CONTINUE;
        }
    }

    return HOOK_CONTINUE;
}

//------------------------------------------------------------
// Disconnect Hook: Clean up when a player leaves.
// Here we call StopModelGlow with removePersistence = false
// so that the player's settings remain saved for when they rejoin.
//------------------------------------------------------------
HookReturnCode ClientDisconnect(CBasePlayer@ pPlayer)
{
    if (pPlayer !is null)
        StopModelGlow(pPlayer, false);
    return HOOK_CONTINUE;
}

//------------------------------------------------------------
// PluginExit: Clean up on map change/shutdown.
//------------------------------------------------------------
void PluginExit()
{
    for (int i = int(glowInfos.length()) - 1; i >= 0; i--)
    {
        GlowInfo@ info = glowInfos[i];
        CBasePlayer@ pPlayer = info.getPlayer();
        if (pPlayer !is null && g_EntityFuncs.IsValidEntity(pPlayer.edict()))
            ResetPlayerGlow(pPlayer);
        glowInfos.removeAt(i);
    }
    
    CBasePlayer@ pPlayer = null;
    for (int i = 1; i <= g_Engine.maxClients; i++)
    {
        @pPlayer = g_PlayerFuncs.FindPlayerByIndex(i);
        if (pPlayer !is null && g_EntityFuncs.IsValidEntity(pPlayer.edict()))
            ResetPlayerGlow(pPlayer);
    }
}

//------------------------------------------------------------
// PluginInit: Register hooks and schedule the update loop.
//------------------------------------------------------------
void PluginInit()
{
    g_Module.ScriptInfo.SetAuthor("grunt");
    g_Module.ScriptInfo.SetContactInfo("sneed.com");
    
    g_Hooks.RegisterHook(Hooks::Player::ClientSay, @ClientSay);
    g_Hooks.RegisterHook(Hooks::Player::PlayerSpawn, @PlayerSpawn);
    g_Hooks.RegisterHook(Hooks::Player::ClientDisconnect, @ClientDisconnect);
    g_Hooks.RegisterHook(Hooks::Player::ClientPutInServer, @ClientPutInServer);
    
    g_Scheduler.SetInterval("RGBModelGlow", 0.1f);
}
