

class Pictomancer : Weapon
{
    private bool initialized;

    private Canvas paint;
    private Canvas display;
    private bool absolute;
    private Color brushColor;
    private int brightness;

    private bool dontCycle;
    private int colorMode;

    private int incrementMultiplier;

    enum ColorModes {
        MODE_RED,
        MODE_GREEN,
        MODE_BLUE,
        MODE_ABSOLUTE,
        MODE_LIGHT,
        MODES_MAX
    }

    Default {
        //$Category Weapons
        //$Title Pictomancer
        Tag "Pictomancer";
        Radius 16;
        Height 32;
        Inventory.PickupMessage "Pict up the Pictomancer!";
        Weapon.SlotNumber 1;
        Weapon.SlotPriority 2500;
        Weapon.SelectionOrder 5000;
        +FloatBob
        +Weapon.NoAlert
        +Weapon.Wimpy_Weapon
        +Weapon.Ammo_Optional
        +Weapon.Alt_Ammo_Optional
        +Weapon.NoAutoAim
        +Weapon.CheatNotWeapon
    }

    States
    {
        Spawn:
            PLAY A -1;
            stop;
        Ready:
            PLAY B 1 A_WeaponReady(WRF_ALLOWRELOAD);
            loop;
        Select:
            PLAY B 1 A_Raise;
            loop;
        Deselect:
            PLAY B 1 A_Lower;
            loop;
        Reload:
            PLAY B 2 {
                if ((player.cmd.buttons & BT_ATTACK) || (player.cmd.buttons & BT_ALTATTACK))
                {
                    invoker.ActivateMode(player.cmd.buttons & BT_ALTATTACK);
                    invoker.dontCycle = true;
                    return;
                }
                
                invoker.incrementMultiplier = 4;
            }
            PLAY B 1 {
                if (player.cmd.buttons & BT_RELOAD)
                    return ResolveState("Reload");

                if (invoker.dontCycle) {
                    invoker.dontCycle = false;
                    return ResolveState(null);
                }

                invoker.CycleMode();
                return ResolveState(null);
            }
            goto Ready;
        Fire:
            PLAY B 5 { invoker.PictomancyLogic(); }
            PLAY B 5 A_ReFire;
            goto Ready;
        AltFire:
            PLAY B 5 { invoker.PictomancyLogic(true); }
            PLAY B 5 A_ReFire;
            goto Ready;
    }

    private void ActivateMode(bool subtraction = false)
    {
        switch(colorMode)
        {
            case MODE_RED:
                brushColor.r = GetIncrementedValue(brushColor.r, subtraction);
                break;
            case MODE_GREEN:
                brushColor.g = GetIncrementedValue(brushColor.g, subtraction);
                break;
            case MODE_BLUE:
                brushColor.b = GetIncrementedValue(brushColor.b, subtraction);
                break;
            case MODE_ABSOLUTE:
                absolute = !absolute;
                break;
            case MODE_LIGHT:
                brightness = GetIncrementedValue(brightness, subtraction, -255);
                break;
        }

        DisplayMode();

        incrementMultiplier++;
    }

    private int GetIncrementedValue(int startValue, bool decrement, int min = 0, int max = 255)
    {
        return clamp(startValue + (decrement ? -0.25 : 0.25) * incrementMultiplier, min, max);
    }

    private void Initialize()
    {
        if (initialized)
            return;

        colorMode = 0;
        paint = TexMan.GetCanvas("JMBPAINT");
        paint.Clear(0, 0, 32, 32, 0xffff00ff);
        display = TexMan.GetCanvas("JMBMODE");
        display.Clear(0, 0, 32, 32, 0xff000000);

        brushColor = Color(0, 0, 0);
        brightness = 0;
        absolute = false;
        incrementMultiplier = 4;

        initialized = true;
    }

    override void PostBeginPlay()
    {
        super.PostBeginPlay();

        Initialize();
    }

    override void AttachToOwner(Actor other)
    {
        super.AttachToOwner(other);

        Initialize();
        DisplayMode();
    }

    private void CycleMode()
    {
        colorMode++;
        if(colorMode >= MODES_MAX)
            colorMode = 0;

        DisplayMode();
    }

    private void DisplayMode()
    {
        paint.Clear(0, 0, 32, 32, brushColor);

        switch(colorMode)
        {
            case MODE_RED:
                display.Clear(0, 0, 32, 32, "red");
                display.DrawText(bigFont, Font.CR_WHITE, 13, 2, "R", DTA_Monospace, Mono_CellCenter);
                display.DrawText(conFont, Font.CR_WHITE, 1, 14, String.Format("%03i", brushColor.r));
                break;
            case MODE_GREEN:
                display.Clear(0, 0, 32, 32, "green");
                display.DrawText(bigFont, Font.CR_WHITE, 13, 2, "G", DTA_Monospace, Mono_CellCenter);
                display.DrawText(conFont, Font.CR_WHITE, 1, 14, String.Format("%03i", brushColor.g));
                break;
            case MODE_BLUE:
                display.Clear(0, 0, 32, 32, "blue");
                display.DrawText(bigFont, Font.CR_WHITE, 13, 2, "B", DTA_Monospace, Mono_CellCenter);
                display.DrawText(conFont, Font.CR_WHITE, 1, 14, String.Format("%03i", brushColor.b));
                break;
            case MODE_ABSOLUTE:
                display.Clear(0, 0, 32, 32, "black");
                display.DrawText(bigFont, Font.CR_WHITE, 13, 2, absolute ? "=" : "+", DTA_Monospace, Mono_CellCenter);
                display.DrawText(conFont, Font.CR_WHITE, 1, 14, absolute ? "ABS" : "REL");
                break;
            case MODE_LIGHT:
                display.Clear(0, 0, 32, 32, Color(255, 255, 128));
                display.DrawText(bigFont, Font.CR_BLACK, 13, 2, "L", DTA_Monospace, Mono_CellCenter);
                display.DrawText(conFont, brightness < 0 ? Font.CR_BLACK : Font.CR_WHITE, 0, 14, String.Format("%03i", abs(brightness)));
                break;
        }
    }

    private void PictomancyLogic(bool sectorMode = false)
    {
        PlayerPawn pp = owner.player.mo;
        FLineTraceData data;

        if(pp.LineTrace(pp.angle, 10000.0, pp.pitch, TRF_THRUHITSCAN | TRF_NOSKY,
            pp.height * 0.5 - pp.floorclip + pp.AttackZOffset * pp.player.crouchFactor, 0.0, 0.0, data))
        {
            Sector targetSector;

            switch(data.HitType)
            {
                case TRACE_HitFloor:
                case TRACE_HitCeiling:
                    if(sectorMode) {
                        targetSector = data.HitSector;
                        break;
                    }
                    
                    if(colorMode == MODE_LIGHT) {
                        data.HitSector.ChangeFlags(data.SectorPlane, absolute ? 0 : 1, absolute ? 1 : 0); // ZScript enum missing? PLANEF_ABSLIGHTING
                        data.HitSector.SetPlaneLight(data.SectorPlane, brightness);
                        break;
                    }

                    data.HitSector.SetSpecialColor(data.SectorPlane, brushColor);
                    break;
                case TRACE_HitWall:
                    if(sectorMode) {
                        targetSector = data.HitLine.sidedef[data.LineSide].sector;
                        break;
                    }

                    if(colorMode == MODE_LIGHT) {
                        if(absolute)
                            data.HitLine.sidedef[data.LineSide].Flags |= Side.WALLF_ABSLIGHTING;
                        else
                            data.HitLine.sidedef[data.LineSide].Flags &= ~Side.WALLF_ABSLIGHTING;
                        data.HitLine.sidedef[data.LineSide].Light = brightness; // No ZScript support for applying light levels to individual wall parts?
                        break;
                    }

                    data.HitLine.sidedef[data.LineSide].SetSpecialColor(data.LinePart, Side.walltop, brushColor, true);
                    data.HitLine.sidedef[data.LineSide].SetSpecialColor(data.LinePart, Side.wallbottom, brushColor, true);
                    break;
                case TRACE_HitActor:
                    if(sectorMode) {
                        targetSector = data.HitActor.CurSector;
                        break;
                    }

                    if(colorMode == MODE_LIGHT) {
                        data.HitActor.LightLevel = brightness;
                        data.HitActor.bADDLIGHTLEVEL = !absolute;
                        break;
                    }

                    int regTID = data.HitActor.TID;
                    data.HitActor.ChangeTid(65536);
                    CallACS("ApplyTranslation", 65536, brushColor.r, brushColor.g, brushColor.b);
                    data.HitActor.ChangeTid(regTID);
                    break;
            }

            if(!targetSector) return;

            if(colorMode == MODE_LIGHT) {
                targetSector.lightlevel = absolute
                    ? brightness
                    : targetSector.lightlevel + brightness;
                return;
            }

            targetSector.SetColor((owner.player.cmd.buttons & BT_USE) ? Color(255, 255, 255) : brushColor);
        }
    }
}