/*
 * HalfNES by Andrew Hoffman
 * Licensed under the GNU GPL Version 3. See LICENSE file
 */
package com.grapeshot.halfnes.mappers;

import com.grapeshot.halfnes.*;

public class MMC1Mapper extends Mapper {

    private int mmc1shift = 0;
    private int mmc1latch = 0;
    private int mmc1ctrl = 0xc;
    private int mmc1chr0 = 0;
    private int mmc1chr1 = 0;
    private int mmc1prg = 0;
    private boolean soromlatch = false;
    private double cpucycleprev = 0; // for Bill and Ted fix
    private long framecountprev = 0;

    @Override
    public void loadrom() throws BadMapperException {
        // needs to be in every mapper. Fill with initial cfg
        super.loadrom();
        for (int i = 0; i < 32; ++i) {
            prg_map[i] = (1024 * i) & (prgsize - 1);
        }
        for (int i = 0; i < 8; ++i) {
            chr_map[i] = (1024 * i) & (chrsize - 1);
        }
        setbanks();
    }

    @Override
    public final void cartWrite(final int addr, final int data) {
        if (addr < 0x8000 || addr > 0xffff) {
            super.cartWrite(addr, data);
            return;
        }
        if (cpu.clocks == cpucycleprev && cpuram.apu.nes.framecount == framecountprev) {
            return; //bill and ted fix - prevents 2 writes too close together
            //from being acknowledged
            //if I ever go to a cycle based core instead of opcode based this needs to change.
        }
        framecountprev = cpuram.apu.nes.framecount;
        //and this is extremely ugly/likely to break
        //but is needed to prevent the Bill+Ted fix from breaking Dr Mario intro.
        cpucycleprev = cpu.clocks;
        if (((data & (utils.BIT7)) != 0)) {
            // reset shift register
            mmc1shift = 0;
            mmc1latch = 0;
            mmc1ctrl |= 0xc;
            setbanks();
            return;
        }

        mmc1shift = (mmc1shift >> 1) + (data & 1) * 16;
        ++mmc1latch;
        // mmc1shift &= 0x1f;
        if (mmc1latch < 5) {
            return; // no need to do anything
        } else {
            if (addr >= 0x8000 && addr <= 0x9fff) {
                // mmc1control
                mmc1ctrl = mmc1shift & 0x1f;
                MirrorType mirtype;
                switch (mmc1ctrl & 3) {
                    case 0:
                        mirtype = MirrorType.SS_MIRROR0;
                        break;
                    case 1:
                        mirtype = MirrorType.SS_MIRROR1;
                        break;
                    case 2:
                        mirtype = MirrorType.V_MIRROR;
                        break;
                    default:
                        mirtype = MirrorType.H_MIRROR;
                        break;
                }
                setmirroring(mirtype);

            } else if (addr >= 0xa000 && addr <= 0xbfff) {
                // mmc1chr0
                mmc1chr0 = mmc1shift & 0x1f;
                if (prgsize > 262144) {
                    //SOROM boards use the high bit of CHR to switch between 1st and last
                    //256k of the PRG ROM
                    mmc1chr0 &= 0xf;
                    soromlatch = ((mmc1shift & (utils.BIT4)) != 0);
                }
            } else if (addr >= 0xc000 && addr <= 0xdfff) {
                // mmc1chr1
                mmc1chr1 = mmc1shift & 0x1f;
                if (prgsize > 262144) {
                    mmc1chr1 &= 0xf;
                }
            } else if (addr >= 0xe000 && addr <= 0xffff) {
                // mmc1prg
                mmc1prg = mmc1shift & 0xf;
            }
            // System.err.println("Mapper Bankswitch: Write " + utils.hex(mmc1shift) + " @ " + utils.hex(addr));
            setbanks();
            mmc1latch = 0;
            mmc1shift = 0;
        }

    }

    private void setbanks() {
        // chr bank 0
        if (((mmc1ctrl & (utils.BIT4)) != 0)) {
            // 4k bank mode
            for (int i = 0; i < 4; ++i) {
                chr_map[i] = (1024 * (i + 4 * mmc1chr0)) % chrsize;
            }
            for (int i = 0; i < 4; ++i) {
                chr_map[i + 4] = (1024 * (i + 4 * mmc1chr1)) % chrsize;
            }
        } else {
            // 8k bank mode
            for (int i = 0; i < 8; ++i) {
                chr_map[i] = (1024 * (i + 8 * (mmc1chr0 >> 1))) % chrsize;
            }
        }

        // prg bank
        if (!((mmc1ctrl & (utils.BIT3)) != 0)) {
            // 32k switch
            // ignore low bank bit
            for (int i = 0; i < 32; ++i) {
                prg_map[i] = (1024 * i + 32768 * (mmc1prg >> 1)) % prgsize;
            }

        } else if (!((mmc1ctrl & (utils.BIT2)) != 0)) {
            // fix 1st bank, 16k switch 2nd bank
            for (int i = 0; i < 16; ++i) {
                prg_map[i] = (1024 * i);
            }
            for (int i = 0; i < 16; ++i) {
                prg_map[i + 16] = (1024 * i + 16384 * mmc1prg) % prgsize;
            }
        } else {
            // fix last bank, switch 1st bank
            for (int i = 0; i < 16; ++i) {
                prg_map[i] = (1024 * i + 16384 * mmc1prg) % prgsize;
            }
            for (int i = 1; i <= 16; ++i) {
                prg_map[32 - i] = (prgsize - (1024 * i));
                if ((prg_map[32 - i]) > 262144) {
                    prg_map[32 - i] -= 262144;
                }
            }
        }
        //if more thn 256k ROM AND SOROM latch is on
        if (soromlatch && (prgsize > 262144)) {
            //add 256k to all of the prg bank #s
            for (int i = 0; i < prg_map.length; ++i) {
                prg_map[i] += 262144;
            }
        }
        //utils.printarray(prg_map);
    }
}
