using SoMRandomizer.config.settings;
using SoMRandomizer.processing.common;

namespace SoMRandomizer.processing.hacks.common.other
{
    /// <summary>
    /// A silly hack to randomize the color of rabites when they load by 
    /// replacing their palette before it goes out to VRAM.
    /// <para />
    /// Generates a random R/G/B between 0-F, adds 0x10 to it, and uses that
    /// as the brightest rabite color.  Darker colors are generated by doing
    /// a little math on the first color.
    /// <para />
    /// Known issues: Poisoning or causing some other status conditions to
    /// a rabite will reset it to the default yellow.
    /// </summary>
    /// 
    /// <remarks>Author: Moppleton</remarks>
    public class RabiteColorRandomizer : RandoProcessor
    {
        protected override string getName()
        {
            return "Rabite color randomizer";
        }

        protected override bool process(byte[] origRom, byte[] outRom, string seed, RandoSettings settings, RandoContext context)
        {
            if(!settings.getBool(CommonSettings.PROPERTYNAME_RABITE_COLOR_RANDOMIZER))
            {
                return false;
            }
            
            // replace:
            // $00/E03E 22 3F 00 C0 JSL $C0003F[$C0:003F]   A:7F05 X:0600 Y:0170 P:envMxdIzC
            // A is 8bit, xy is 16bit
            // need to preserve A and X, but don't think Y matters
            outRom[0xE03E] = 0x22;
            outRom[0xE03F] = (byte)context.workingOffset;
            outRom[0xE040] = (byte)(context.workingOffset >> 8);
            outRom[0xE041] = (byte)((context.workingOffset >> 16) + 0xC0);

            // REP 20
            outRom[context.workingOffset++] = 0xC2;
            outRom[context.workingOffset++] = 0x20;

            // PHA
            outRom[context.workingOffset++] = 0x48;

            // SEP 20
            outRom[context.workingOffset++] = 0xE2;
            outRom[context.workingOffset++] = 0x20;

            // LDA $E180,x[$7E:E380] - load species (8 bit)
            outRom[context.workingOffset++] = 0xBD;
            outRom[context.workingOffset++] = 0x80;
            outRom[context.workingOffset++] = 0xE1;

            // CMP #00
            outRom[context.workingOffset++] = 0xC9;
            outRom[context.workingOffset++] = 0x00;

            // BNE end - not a rabite; don't do anything
            outRom[context.workingOffset++] = 0xF0;
            outRom[context.workingOffset++] = 0x0A;

            // REP 20
            outRom[context.workingOffset++] = 0xC2;
            outRom[context.workingOffset++] = 0x20;

            // PLA - clean up stack, restore A
            outRom[context.workingOffset++] = 0x68;

            // SEP 20
            outRom[context.workingOffset++] = 0xE2;
            outRom[context.workingOffset++] = 0x20;

            // replaced code
            outRom[context.workingOffset++] = 0x22;
            outRom[context.workingOffset++] = 0x3F;
            outRom[context.workingOffset++] = 0x00;
            outRom[context.workingOffset++] = 0xC0;

            // RTL
            outRom[context.workingOffset++] = 0x6B;

            // over:
            // LDY $E0F8,x - load palette offset
            outRom[context.workingOffset++] = 0xBC;
            outRom[context.workingOffset++] = 0xF8;
            outRom[context.workingOffset++] = 0xE0;

            // ////////////////////////////////////////////
            // generate a random color for the rabite, by calling the vanilla RNG subroutine, and
            // "and"ing the result with 0x0F.  Do this three times, one for red, one for green, one for
            // blue.  Add 0x10 to each value, since it will be scaled down to make darker colors.
            //
            // Scale this RGB value down to make darker versions of the same color for different
            // palette indexes.
            // Used for indexes 2,11,12,13,14; locations x02,x14,16,18,1A
            // 
            // note: snes color format: xBBBBBGG GGGRRRRR (msb, lsb)

            // ///////////////////////////////////////
            // generate random red value 0x10 - 0x1F
            // ///////////////////////////////////////
            callRandomSubr(outRom, ref context.workingOffset);

            // AND #0F
            outRom[context.workingOffset++] = 0x29;
            outRom[context.workingOffset++] = 0x1F;

            // CLC
            outRom[context.workingOffset++] = 0x18;

            // ADC #10
            outRom[context.workingOffset++] = 0x69;
            outRom[context.workingOffset++] = 0x08;

            // CMP #1F
            outRom[context.workingOffset++] = 0xC9;
            outRom[context.workingOffset++] = 0x20;

            // BCC over
            outRom[context.workingOffset++] = 0x90;
            outRom[context.workingOffset++] = 0x02;

            outRom[context.workingOffset++] = 0xA9;
            outRom[context.workingOffset++] = 0x1F;

            // STA $DF1A,y  (lsb = red/green byte)
            outRom[context.workingOffset++] = 0x99;
            outRom[context.workingOffset++] = 0x1A;
            outRom[context.workingOffset++] = 0xDF;

            // ///////////////////////////////////////
            // generate random green value 0x10 - 0x1F
            // ///////////////////////////////////////

            // green is the complicated one since it spans both bytes
            callRandomSubr(outRom, ref context.workingOffset);

            // AND #0F
            outRom[context.workingOffset++] = 0x29;
            outRom[context.workingOffset++] = 0x1F;

            // CLC
            outRom[context.workingOffset++] = 0x18;

            // ADC #10
            outRom[context.workingOffset++] = 0x69;
            outRom[context.workingOffset++] = 0x08;

            // CMP #1F
            outRom[context.workingOffset++] = 0xC9;
            outRom[context.workingOffset++] = 0x20;

            // BCC over
            outRom[context.workingOffset++] = 0x90;
            outRom[context.workingOffset++] = 0x02;

            // LDA #1F
            outRom[context.workingOffset++] = 0xA9;
            outRom[context.workingOffset++] = 0x1F;

            // PHA - save off the green value since we need to split it up
            outRom[context.workingOffset++] = 0x48;

            // ASL x 5
            outRom[context.workingOffset++] = 0x0A;
            outRom[context.workingOffset++] = 0x0A;
            outRom[context.workingOffset++] = 0x0A;
            outRom[context.workingOffset++] = 0x0A;
            outRom[context.workingOffset++] = 0x0A;

            // ORA $DF1A,y  (lsb = red/green byte) - "or" with the red value stored earlier
            outRom[context.workingOffset++] = 0x19;
            outRom[context.workingOffset++] = 0x1A;
            outRom[context.workingOffset++] = 0xDF;

            // STA $DF1A,y  (lsb = red/green byte)
            outRom[context.workingOffset++] = 0x99;
            outRom[context.workingOffset++] = 0x1A;
            outRom[context.workingOffset++] = 0xDF;

            // PLA - pull out the saved green value
            outRom[context.workingOffset++] = 0x68;

            // LSR x 3
            outRom[context.workingOffset++] = 0x4A;
            outRom[context.workingOffset++] = 0x4A;
            outRom[context.workingOffset++] = 0x4A;

            // STA $DF1B,y  (msb = green/blue byte)
            outRom[context.workingOffset++] = 0x99;
            outRom[context.workingOffset++] = 0x1B;
            outRom[context.workingOffset++] = 0xDF;

            // ///////////////////////////////////////
            // generate random blue value 0x10 - 0x1F
            // ///////////////////////////////////////
            callRandomSubr(outRom, ref context.workingOffset);

            // AND #0F
            outRom[context.workingOffset++] = 0x29;
            outRom[context.workingOffset++] = 0x1F;

            // CLC
            outRom[context.workingOffset++] = 0x18;

            // ADC #10
            outRom[context.workingOffset++] = 0x69;
            outRom[context.workingOffset++] = 0x08;

            // CMP #1F
            outRom[context.workingOffset++] = 0xC9;
            outRom[context.workingOffset++] = 0x20;

            // BCC over
            outRom[context.workingOffset++] = 0x90;
            outRom[context.workingOffset++] = 0x02;

            // LDA #1F
            outRom[context.workingOffset++] = 0xA9;
            outRom[context.workingOffset++] = 0x1F;

            // over:
            // ASL
            outRom[context.workingOffset++] = 0x0A;
            // ASL
            outRom[context.workingOffset++] = 0x0A;

            // 19 1B DF   ORA $DF1B, y - or w/ green
            outRom[context.workingOffset++] = 0x19;
            outRom[context.workingOffset++] = 0x1B;
            outRom[context.workingOffset++] = 0xDF;

            // 99 1B DF   STA $DF1B, y - msb green+blue
            outRom[context.workingOffset++] = 0x99;
            outRom[context.workingOffset++] = 0x1B;
            outRom[context.workingOffset++] = 0xDF;

            // generate darker versions of the color for other indexes
            darkenTo75Percent(outRom, ref context.workingOffset, 0xDF1A, 0xDF18);
            darkenTo75Percent(outRom, ref context.workingOffset, 0xDF18, 0xDF16);
            darkenTo75Percent(outRom, ref context.workingOffset, 0xDF16, 0xDF14);
            darkenTo75Percent(outRom, ref context.workingOffset, 0xDF14, 0xDF02);

            // for now, the white = the color before it
            outRom[context.workingOffset++] = 0xB9; // LDA
            outRom[context.workingOffset++] = 0x1A;
            outRom[context.workingOffset++] = 0xDF;
            outRom[context.workingOffset++] = 0x99; // STA
            outRom[context.workingOffset++] = 0x1C;
            outRom[context.workingOffset++] = 0xDF;
            outRom[context.workingOffset++] = 0xB9; // LDA
            outRom[context.workingOffset++] = 0x1B;
            outRom[context.workingOffset++] = 0xDF;
            outRom[context.workingOffset++] = 0x99; // STA
            outRom[context.workingOffset++] = 0x1D;
            outRom[context.workingOffset++] = 0xDF;

            // copy DF1A values to 071A .. 14,15,16,17,18,19,1A,1B
            // the four yellowish colors in OG rabite palette
            for (byte lsb = 0x14; lsb <= 0x1D; lsb++)
            {
                outRom[context.workingOffset++] = 0xB9; // LDA
                outRom[context.workingOffset++] = lsb;
                outRom[context.workingOffset++] = 0xDF;
                outRom[context.workingOffset++] = 0x99; // STA
                outRom[context.workingOffset++] = lsb;
                outRom[context.workingOffset++] = 0x07;
            }

            // this one, too - gold tinted outline of OG rabite
            for (byte lsb = 0x02; lsb <= 0x03; lsb++)
            {
                outRom[context.workingOffset++] = 0xB9; // LDA
                outRom[context.workingOffset++] = lsb;
                outRom[context.workingOffset++] = 0xDF;
                outRom[context.workingOffset++] = 0x99; // STA
                outRom[context.workingOffset++] = lsb;
                outRom[context.workingOffset++] = 0x07;
            }

            // REP 20
            outRom[context.workingOffset++] = 0xC2;
            outRom[context.workingOffset++] = 0x20;

            // PLA
            outRom[context.workingOffset++] = 0x68;

            // SEP 20
            outRom[context.workingOffset++] = 0xE2;
            outRom[context.workingOffset++] = 0x20;

            // JSL $C0003F - replaced code
            outRom[context.workingOffset++] = 0x22;
            outRom[context.workingOffset++] = 0x3F;
            outRom[context.workingOffset++] = 0x00;
            outRom[context.workingOffset++] = 0xC0;

            // RTL
            outRom[context.workingOffset++] = 0x6B;

            // this is a block that resets palettes when the target is poisoned - presumably only for players,
            // but it applies to every object here.  doing this re-loads the vanilla palette for rabites,
            // so modify it to skip the load for object type 0 == rabite.

            // $01/946A 20 22 E0    JSR $E022  [$01:E022]   A:2000 X:0600 Y:2000 P:envMxdIZC
            // $01/946D E2 20       SEP #$20                A:7FFF X:0600 Y:2000 P:envmxdIzC

            outRom[0x1946A] = 0x22;
            outRom[0x1946B] = (byte)context.workingOffset;
            outRom[0x1946C] = (byte)(context.workingOffset >> 8);
            outRom[0x1946D] = (byte)((context.workingOffset >> 16) + 0xC0);
            outRom[0x1946E] = 0xEA;

            // LDA 7EE180,X
            outRom[context.workingOffset++] = 0xBF;
            outRom[context.workingOffset++] = 0x80;
            outRom[context.workingOffset++] = 0xE1;
            outRom[context.workingOffset++] = 0x7E;
            // BNE over [01]
            outRom[context.workingOffset++] = 0xD0;
            outRom[context.workingOffset++] = 0x01;
            // RTL
            outRom[context.workingOffset++] = 0x6B;
            // over:
            // copy vanilla subroutine: 0x01E022->0x01E06E
            for (int i=0x1E022; i <= 0x1E06E; i++)
            {
                byte newByte = outRom[i];
                if(newByte == 0x60)
                {
                    // SEP 20 - replaced code
                    outRom[context.workingOffset++] = 0xE2;
                    outRom[context.workingOffset++] = 0x20;
                    // RTL
                    newByte = 0x6B;
                }
                outRom[context.workingOffset++] = newByte;
            }

            return true;
        }


        private void darkenTo75Percent(byte[] outRom, ref int newCodeOffset, ushort srcColorOffset, ushort destColorOffset)
        {
            // //////////////////
            // RED
            // //////////////////

            // LDA srcColorOffset,y  (lsb = red/green byte)
            outRom[newCodeOffset++] = 0xB9;
            outRom[newCodeOffset++] = (byte)srcColorOffset;
            outRom[newCodeOffset++] = (byte)(srcColorOffset>>8);

            // AND #1F - isolate the red component
            outRom[newCodeOffset++] = 0x29;
            outRom[newCodeOffset++] = 0x1F;

            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;

            // PHA - push value * .25
            outRom[newCodeOffset++] = 0x48;

            // LDA srcColorOffset,y  (lsb = red/green byte)
            outRom[newCodeOffset++] = 0xB9;
            outRom[newCodeOffset++] = (byte)srcColorOffset;
            outRom[newCodeOffset++] = (byte)(srcColorOffset >> 8);

            // AND #1F - isolate the red component
            outRom[newCodeOffset++] = 0x29;
            outRom[newCodeOffset++] = 0x1F;

            // SEC - prepare to subtract
            outRom[newCodeOffset++] = 0x38;

            // SBC $01,s - subtract value * .25 from full value
            outRom[newCodeOffset++] = 0xE3;
            outRom[newCodeOffset++] = 0x01;

            // STA $01,s - stick back into stack
            outRom[newCodeOffset++] = 0x83;
            outRom[newCodeOffset++] = 0x01;

            // PLA - extract 75% scaled value and even out the stack
            outRom[newCodeOffset++] = 0x68;

            // STA destColorOffset,y  (lsb = red/green byte)
            outRom[newCodeOffset++] = 0x99;
            outRom[newCodeOffset++] = (byte)destColorOffset;
            outRom[newCodeOffset++] = (byte)(destColorOffset >> 8);


            // //////////////////
            // GREEN
            // //////////////////

            // again, green is the complicated one, because it spans both color bytes.

            // LDA srcColorOffset,y  (lsb = red/green byte)
            outRom[newCodeOffset++] = 0xB9;
            outRom[newCodeOffset++] = (byte)srcColorOffset;
            outRom[newCodeOffset++] = (byte)(srcColorOffset >> 8);

            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // PHA - save off partially created green value
            outRom[newCodeOffset++] = 0x48;

            // LDA srcColorOffset+1,y  (msb = green/blue byte)
            outRom[newCodeOffset++] = 0xB9;
            outRom[newCodeOffset++] = (byte)(srcColorOffset+1);
            outRom[newCodeOffset++] = (byte)((srcColorOffset+1) >> 8);

            // AND #03
            outRom[newCodeOffset++] = 0x29;
            outRom[newCodeOffset++] = 0x03;

            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ASL
            outRom[newCodeOffset++] = 0x0A;

            // ORA $01,s - "or" with the partial value off the first byte
            outRom[newCodeOffset++] = 0x03;
            outRom[newCodeOffset++] = 0x01;

            // STA $01,s - write the full green value back to the stack
            outRom[newCodeOffset++] = 0x83;
            outRom[newCodeOffset++] = 0x01;

            // PLA - pull the same value back out to even out the stack
            outRom[newCodeOffset++] = 0x68;

            // PHA - push back original green value 
            outRom[newCodeOffset++] = 0x48;

            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;

            // PHA - push value * .25
            outRom[newCodeOffset++] = 0x48;

            // A3 02  LDA $02,s - load full value from back earlier
            outRom[newCodeOffset++] = 0xA3;
            outRom[newCodeOffset++] = 0x02;

            // MOPPLE: shouldn't this code be SEC-ing before SBC? is the green slightly off because of this?

            // SBC $01,s - subtract value * .25 that was pushed to the stack from full value
            outRom[newCodeOffset++] = 0xE3;
            outRom[newCodeOffset++] = 0x01;

            // STA $01,s - store back into the stack
            outRom[newCodeOffset++] = 0x83;
            outRom[newCodeOffset++] = 0x01;

            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ASL
            outRom[newCodeOffset++] = 0x0A;

            // ORA destColorOffset,y - "or" w/ red value
            outRom[newCodeOffset++] = 0x19;
            outRom[newCodeOffset++] = (byte)(destColorOffset);
            outRom[newCodeOffset++] = (byte)((destColorOffset) >> 8);

            // STA destColorOffset,y  (lsb = red/green byte)
            outRom[newCodeOffset++] = 0x99;
            outRom[newCodeOffset++] = (byte)(destColorOffset);
            outRom[newCodeOffset++] = (byte)((destColorOffset) >> 8);

            // PLA - grab 75% value again
            outRom[newCodeOffset++] = 0x68;

            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;

            // STA destColorOffset+1,y  (msb = green/blue byte)
            outRom[newCodeOffset++] = 0x99;
            outRom[newCodeOffset++] = (byte)(destColorOffset + 1);
            outRom[newCodeOffset++] = (byte)((destColorOffset + 1) >> 8);

            // PLA - even out the stack
            outRom[newCodeOffset++] = 0x68;


            // //////////////////
            // BLUE
            // //////////////////

            // LDA srcColorOffset+1, y  (msb = green/blue byte)
            outRom[newCodeOffset++] = 0xB9;
            outRom[newCodeOffset++] = (byte)(srcColorOffset + 1);
            outRom[newCodeOffset++] = (byte)((srcColorOffset + 1) >> 8);

            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;

            // PHA - push value * .25
            outRom[newCodeOffset++] = 0x48;

            // LDA srcColorOffset+1, y  (msb = green/blue byte)
            outRom[newCodeOffset++] = 0xB9;
            outRom[newCodeOffset++] = (byte)(srcColorOffset + 1);
            outRom[newCodeOffset++] = (byte)((srcColorOffset + 1) >> 8);

            // LSR
            outRom[newCodeOffset++] = 0x4A;
            // LSR
            outRom[newCodeOffset++] = 0x4A;

            // SEC
            outRom[newCodeOffset++] = 0x38;

            // E3 01  SBC $01,s - subtract value * .25 from full value
            outRom[newCodeOffset++] = 0xE3;
            outRom[newCodeOffset++] = 0x01;

            // 83 01  STA $01,s - stick back into stack then pull out
            outRom[newCodeOffset++] = 0x83;
            outRom[newCodeOffset++] = 0x01;

            // PLA
            outRom[newCodeOffset++] = 0x68;

            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ASL
            outRom[newCodeOffset++] = 0x0A;
            // ORA destColorOffset+1, y - "or" w/ existing green value from earlier
            outRom[newCodeOffset++] = 0x19;
            outRom[newCodeOffset++] = (byte)(destColorOffset + 1);
            outRom[newCodeOffset++] = (byte)((destColorOffset + 1) >> 8);

            // STA destColorOffset+1, y  (msb = green/blue byte)
            outRom[newCodeOffset++] = 0x99;
            outRom[newCodeOffset++] = (byte)(destColorOffset + 1);
            outRom[newCodeOffset++] = (byte)((destColorOffset + 1) >> 8);
        }

        private void callRandomSubr(byte[] outRom, ref int newCodeOffset)
        {
            // call the vanilla RNG subroutine, which puts a "random" value 00-FF into A

            // phd
            outRom[newCodeOffset++] = 0x0B;
            // rep 20
            outRom[newCodeOffset++] = 0xC2;
            outRom[newCodeOffset++] = 0x20;
            // LDA #0300
            outRom[newCodeOffset++] = 0xA9;
            outRom[newCodeOffset++] = 0x00;
            outRom[newCodeOffset++] = 0x03;
            // PHA
            outRom[newCodeOffset++] = 0x48;
            // PLD
            outRom[newCodeOffset++] = 0x2B;
            // sep 20
            outRom[newCodeOffset++] = 0xE2;
            outRom[newCodeOffset++] = 0x20;
            // JSL
            outRom[newCodeOffset++] = 0x22;
            outRom[newCodeOffset++] = 0x9C;
            outRom[newCodeOffset++] = 0x38;
            outRom[newCodeOffset++] = 0xC0;
            // PLD
            outRom[newCodeOffset++] = 0x2B;
        }
    }
}
