// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or(at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#include <mednafen/mednafen.h>
#include <mednafen/mempatcher.h>
#include <mednafen/Time.h>
#include "gbGlobals.h"
#include "memory.h"

namespace MDFN_IEN_GB
{

mapperMBC1 gbDataMBC1;

static void SetROMMap(uint32 tmpAddress)
{
 tmpAddress &= gbRomSizeMask;
 gbMemoryMap[0x04] = &gbRom[tmpAddress + 0x0000];
 gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
 gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
 gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
}

static void SetRAM8K(uint8 bank)
{
 if(gbRamSize)
 {
  gbMemoryMap[0x0a] = &gbRam[((bank << 13) + 0x0000) & gbRamSizeMask];
  gbMemoryMap[0x0b] = &gbRam[((bank << 13) + 0x1000) & gbRamSizeMask];
  MDFNMP_AddRAM((gbRamSize > 8192) ? 8192 : gbRamSize, 0xA000, &gbRam[(bank << 13) & gbRamSizeMask]);
 }
}

void memoryUpdateMapMBC1()
{
  int tmpAddress = gbDataMBC1.mapperROMBank << 14;

  // check current model
  if(gbDataMBC1.mapperMemoryModel == 0) {
    // model is 16/8, so we have a high address in use
    tmpAddress |= gbDataMBC1.mapperRAMBank << 19;
  }

  SetROMMap(tmpAddress);

  if(gbDataMBC1.mapperMemoryModel == 1)
   SetRAM8K(gbDataMBC1.mapperRAMBank);
  else
   SetRAM8K(0);

}

// MBC1 ROM write registers
void mapperMBC1ROM(uint16 address, uint8 value)
{
  switch(address & 0x6000) 
  {
   case 0x0000: // RAM enable register
    gbDataMBC1.mapperRAMEnable = ( ( value & 0x0a) == 0x0a ? 1 : 0);
    break;

   case 0x2000: // ROM bank select
    //    value = value & 0x1f;
    if((value & 0x1f) == 0)
      value++;

    gbDataMBC1.mapperROMBank = value;
    memoryUpdateMapMBC1();
    break;

  case 0x4000: // RAM bank select
    gbDataMBC1.mapperRAMBank = value & 0x03;
    memoryUpdateMapMBC1();
    break;

  case 0x6000: // memory model select
    gbDataMBC1.mapperMemoryModel = value & 1;
    memoryUpdateMapMBC1();
    break;
  }
}

// MBC1 RAM write
void mapperMBC1RAM(uint16 address, uint8 value)
{
  if(gbDataMBC1.mapperRAMEnable) {
    if(gbRamSize) {
      gbMemoryMap[address >> 12][address & 0x0fff & gbRamSizeMask] = value;
    }
  }
}

mapperMBC2 gbDataMBC2;

// MBC2 ROM write registers
void mapperMBC2ROM(uint16 address, uint8 value)
{
  switch(address & 0x6000) {
  case 0x0000: // RAM enable
    if(!(address & 0x0100)) {
      gbDataMBC2.mapperRAMEnable = (value & 0x0f) == 0x0a;
    }
    break;
  case 0x2000: // ROM bank select
    if(address & 0x0100) {
      value &= 0x0f;

      if(value == 0)
        value = 1;
      if(gbDataMBC2.mapperROMBank != value) {
        gbDataMBC2.mapperROMBank = value;
	SetROMMap(value << 14);
      }
    }
    break;
  }
}

// MBC2 RAM write
void mapperMBC2RAM(uint16 address, uint8 value)
{
  if(gbDataMBC2.mapperRAMEnable) {
    if(gbRamSize && address < 0xa200) {
      gbMemoryMap[address >> 12][address & 0x0fff & gbRamSizeMask] = value;
    }
  }
}

void memoryUpdateMapMBC2()
{
  SetROMMap(gbDataMBC2.mapperROMBank << 14);
}

mapperMBC3 gbDataMBC3;

void memoryUpdateMBC3Clock()
{
  int64 now = Time::EpochTime();
  int64 diff = now - gbDataMBC3.mapperLastTime;
  if(diff > 0) {
    // update the clock according to the last update time
    gbDataMBC3.mapperSeconds += diff % 60;
    if(gbDataMBC3.mapperSeconds > 59) {
      gbDataMBC3.mapperSeconds -= 60;
      gbDataMBC3.mapperMinutes++;
    }

    diff /= 60;

    gbDataMBC3.mapperMinutes += diff % 60;
    if(gbDataMBC3.mapperMinutes > 60) {
      gbDataMBC3.mapperMinutes -= 60;
      gbDataMBC3.mapperHours++;
    }

    diff /= 60;

    gbDataMBC3.mapperHours += diff % 24;
    if(gbDataMBC3.mapperHours > 24) {
      gbDataMBC3.mapperHours -= 24;
      gbDataMBC3.mapperDays++;
    }
    diff /= 24;

    gbDataMBC3.mapperDays += diff;
    if(gbDataMBC3.mapperDays > 255) {
      if(gbDataMBC3.mapperDays > 511) {
        gbDataMBC3.mapperDays %= 512;
        gbDataMBC3.mapperControl |= 0x80;
      }
      gbDataMBC3.mapperControl = (gbDataMBC3.mapperControl & 0xfe) |
        (gbDataMBC3.mapperDays>255 ? 1 : 0);
    }
  }
  gbDataMBC3.mapperLastTime = now;
}

// MBC3 ROM write registers
void mapperMBC3ROM(uint16 address, uint8 value)
{
  switch(address & 0x6000)
  {
  case 0x0000: // RAM enable register
    gbDataMBC3.mapperRAMEnable = ( ( value & 0x0a) == 0x0a ? 1 : 0);
    break;
  case 0x2000: // ROM bank select
    value = value & 0x7f;
    if(value == 0)
      value = 1;
    if(value == gbDataMBC3.mapperROMBank)
      break;

    SetROMMap(value << 14);
    gbDataMBC3.mapperROMBank = value;
    break;

  case 0x4000: // RAM bank select
    if(value < 8) {
      if(value == gbDataMBC3.mapperRAMBank)
        break;

      SetRAM8K(value);
      gbDataMBC3.mapperRAMBank = value;
    } else {
      if(gbDataMBC3.mapperRAMEnable) {
        gbDataMBC3.mapperRAMBank = -1;

        gbDataMBC3.mapperClockRegister = value;
      }
    }
    break;
  case 0x6000: // clock latch
    if(gbDataMBC3.mapperClockLatch == 0 && value == 1) {
      memoryUpdateMBC3Clock();
      gbDataMBC3.mapperLSeconds = gbDataMBC3.mapperSeconds;
      gbDataMBC3.mapperLMinutes = gbDataMBC3.mapperMinutes;
      gbDataMBC3.mapperLHours   = gbDataMBC3.mapperHours;
      gbDataMBC3.mapperLDays    = gbDataMBC3.mapperDays;
      gbDataMBC3.mapperLControl = gbDataMBC3.mapperControl;
    }
    if(value == 0x00 || value == 0x01)
      gbDataMBC3.mapperClockLatch = value;
    break;
  }
}

// MBC3 RAM write
void mapperMBC3RAM(uint16 address, uint8 value)
{
  if(gbDataMBC3.mapperRAMEnable) {
    if(gbDataMBC3.mapperRAMBank != -1) {
      if(gbRamSize) {
        gbMemoryMap[address>>12][address & 0x0fff & gbRamSizeMask] = value;
      }
    } else {
      gbDataMBC3.mapperLastTime = Time::EpochTime();
      switch(gbDataMBC3.mapperClockRegister) {
      case 0x08:
        gbDataMBC3.mapperSeconds = value;
        break;
      case 0x09:
        gbDataMBC3.mapperMinutes = value;
        break;
      case 0x0a:
        gbDataMBC3.mapperHours = value;
        break;
      case 0x0b:
        gbDataMBC3.mapperDays = value;
        break;
      case 0x0c:
        if(gbDataMBC3.mapperControl & 0x80)
          gbDataMBC3.mapperControl = 0x80 | value;
        else
          gbDataMBC3.mapperControl = value;
        break;
      }
    }
  }
}

// MBC3 read RAM
uint8 mapperMBC3ReadRAM(uint16 address)
{
  if(gbDataMBC3.mapperRAMEnable) {
    if(gbDataMBC3.mapperRAMBank != -1) {
      return gbMemoryMap[address>>12][address & 0x0fff];
    }

    switch(gbDataMBC3.mapperClockRegister) {
      case 0x08:
        return gbDataMBC3.mapperLSeconds;
        break;
      case 0x09:
        return gbDataMBC3.mapperLMinutes;
        break;
      case 0x0a:
        return gbDataMBC3.mapperLHours;
        break;
      case 0x0b:
        return gbDataMBC3.mapperLDays;
        break;
      case 0x0c:
        return gbDataMBC3.mapperLControl;
    }
  }
  return 0;
}

void memoryUpdateMapMBC3()
{
  SetROMMap(gbDataMBC3.mapperROMBank << 14);

  if(gbDataMBC3.mapperRAMBank >= 0 && gbRamSize)
  {
   SetRAM8K(gbDataMBC3.mapperRAMBank);
  }
}

mapperMBC5 gbDataMBC5;

// MBC5 ROM write registers
void mapperMBC5ROM(uint16 address, uint8 value)
{
  switch(address & 0x6000)
  {

  case 0x0000: // RAM enable register
    gbDataMBC5.mapperRAMEnable = ( ( value & 0x0a) == 0x0a ? 1 : 0);
    break;

  case 0x2000: // ROM bank select
    if(address < 0x3000) {
      value = value & 0xff;
      if(value == gbDataMBC5.mapperROMBank)
        break;

      gbDataMBC5.mapperROMBank = value;
      SetROMMap((value << 14) | (gbDataMBC5.mapperROMHighAddress << 22));
    } else {
      value = value & 1;
      if(value == gbDataMBC5.mapperROMHighAddress)
        break;

      gbDataMBC5.mapperROMHighAddress = value;
      SetROMMap((gbDataMBC5.mapperROMBank << 14) | (value << 22));
    }
    break;

  case 0x4000: // RAM bank select
    if(gbDataMBC5.isRumbleCartridge)
      value &= 0x07;
    else
      value &= 0x0f;
    if(value == gbDataMBC5.mapperRAMBank)
      break;

    gbDataMBC5.mapperRAMBank = value;
    SetRAM8K(value);
    break;
  }
}

// MBC5 RAM write
void mapperMBC5RAM(uint16 address, uint8 value)
{
  if(gbDataMBC5.mapperRAMEnable) {
    if(gbRamSize) {
      gbMemoryMap[address >> 12][address & 0x0fff & gbRamSizeMask] = value;
    }
  }
}

void memoryUpdateMapMBC5()
{
  SetROMMap((gbDataMBC5.mapperROMBank << 14) | (gbDataMBC5.mapperROMHighAddress << 22));
  SetRAM8K(gbDataMBC5.mapperRAMBank);
}

mapperMBC7 gbDataMBC7;

// MBC7 ROM write registers
void mapperMBC7ROM(uint16 address, uint8 value)
{
  switch(address & 0x6000) {

  case 0x0000:
    break;

  case 0x2000: // ROM bank select
    value = value & 0x7f;
    if(value == 0)
      value = 1;

    if(value == gbDataMBC7.mapperROMBank)
      break;

    gbDataMBC7.mapperROMBank = value;
    SetROMMap(value << 14);
    break;

  case 0x4000: // ?
    break;
  }
}

// MBC7 read RAM
uint8 mapperMBC7ReadRAM(uint16 address)
{
  switch(address & 0xa0f0) {
  case 0xa000:
  case 0xa010:
  case 0xa060:
  case 0xa070:
    return 0;

  case 0xa020:
    // sensor X low byte
    return((gbDataMBC7.curtiltx >> 0) & 0xFF);

  case 0xa030:
    // sensor X high byte
    return((gbDataMBC7.curtiltx >> 8) & 0xFF);

  case 0xa040:
    // sensor Y low byte
    return((gbDataMBC7.curtilty >> 0) & 0xFF);

  case 0xa050:
    // sensor Y high byte
    return((gbDataMBC7.curtilty >> 8) & 0xFF);

  case 0xa080:
    return gbDataMBC7.value;
  }
  return 0xff;
}

// MBC7 RAM write
void mapperMBC7RAM(uint16 address, uint8 value)
{
  if(address == 0xa080) {
    // special processing needed
    int oldCs = gbDataMBC7.cs,oldSk=gbDataMBC7.sk;
    
    gbDataMBC7.cs=value>>7;
    gbDataMBC7.sk=(value>>6)&1;
    
    if(!oldCs && gbDataMBC7.cs) {
      if(gbDataMBC7.state==5) {
        if(gbDataMBC7.writeEnable) {
          gbRam[gbDataMBC7.address*2]=gbDataMBC7.buffer>>8;
          gbRam[gbDataMBC7.address*2+1]=gbDataMBC7.buffer&0xff;
        }
        gbDataMBC7.state=0;
        gbDataMBC7.value=1;
      } else {
        gbDataMBC7.idle=true;
        gbDataMBC7.state=0;
      }
    }
    
    if(!oldSk && gbDataMBC7.sk) {
      if(gbDataMBC7.idle) {
        if(value & 0x02) {
          gbDataMBC7.idle=false;
          gbDataMBC7.count=0;
          gbDataMBC7.state=1;
        }
      } else {
        switch(gbDataMBC7.state) {
        case 1:
          // receiving command
          gbDataMBC7.buffer <<= 1;
          gbDataMBC7.buffer |= (value & 0x02)?1:0;
          gbDataMBC7.count++;
          if(gbDataMBC7.count==2) {
            // finished receiving command
            gbDataMBC7.state=2;
            gbDataMBC7.count=0;
            gbDataMBC7.code=gbDataMBC7.buffer & 3;
          }
          break;
        case 2:
          // receive address
          gbDataMBC7.buffer <<= 1;
          gbDataMBC7.buffer |= (value&0x02)?1:0;
          gbDataMBC7.count++;
          if(gbDataMBC7.count==8) {
          // finish receiving
            gbDataMBC7.state=3;
            gbDataMBC7.count=0;
            gbDataMBC7.address=gbDataMBC7.buffer&0xff;
            if(gbDataMBC7.code==0) {
              if((gbDataMBC7.address>>6)==0) {
                gbDataMBC7.writeEnable=0;
                gbDataMBC7.state=0;
              } else if((gbDataMBC7.address>>6) == 3) {
                gbDataMBC7.writeEnable=1;
                gbDataMBC7.state=0;
              }
            }
          }
          break;
        case 3:
          gbDataMBC7.buffer <<= 1;
          gbDataMBC7.buffer |= (value&0x02)?1:0;
          gbDataMBC7.count++;
          
          switch(gbDataMBC7.code) {
          case 0:
            if(gbDataMBC7.count==16) {
              if((gbDataMBC7.address>>6)==0) {
                gbDataMBC7.writeEnable = 0;
                gbDataMBC7.state=0;
              } else if((gbDataMBC7.address>>6)==1) {
                if (gbDataMBC7.writeEnable) {
                  for(int i=0;i<256;i++) {
                    gbRam[i*2] = gbDataMBC7.buffer >> 8;
                    gbRam[i*2+1] = gbDataMBC7.buffer & 0xff;
                  }
                }
                gbDataMBC7.state=5;
              } else if((gbDataMBC7.address>>6) == 2) {
                if (gbDataMBC7.writeEnable) {
                  for(int i=0;i<256;i++)
                    *((uint16 *)&gbRam[i*2]) = 0xffff;
                }
                gbDataMBC7.state=5;
              } else if((gbDataMBC7.address>>6)==3) {
                gbDataMBC7.writeEnable = 1;
                gbDataMBC7.state=0;
              }
              gbDataMBC7.count=0;
            }
            break;
          case 1:
            if(gbDataMBC7.count==16) {
              gbDataMBC7.count=0;
              gbDataMBC7.state=5;
              gbDataMBC7.value=0;
            }
            break;
          case 2:
            if(gbDataMBC7.count==1) {
              gbDataMBC7.state=4;
              gbDataMBC7.count=0;
              gbDataMBC7.buffer = (gbRam[gbDataMBC7.address*2]<<8)|
                (gbRam[gbDataMBC7.address*2+1]);
            }
            break;
          case 3:
            if(gbDataMBC7.count==16) {
              gbDataMBC7.count=0;
              gbDataMBC7.state=5;
              gbDataMBC7.value=0;
              gbDataMBC7.buffer=0xffff;
            }
            break;
          }
          break;
        }
      }
    }
    
    if (oldSk && !gbDataMBC7.sk) {
      if (gbDataMBC7.state==4) { 
        gbDataMBC7.value = (gbDataMBC7.buffer & 0x8000)?1:0;
        gbDataMBC7.buffer <<= 1;
        gbDataMBC7.count++;
        if (gbDataMBC7.count==16) {
          gbDataMBC7.count=0;
          gbDataMBC7.state=0;
        }
      }
    }
  }
}

void memoryUpdateMapMBC7()
{
  SetROMMap(gbDataMBC7.mapperROMBank << 14);
}

mapperHuC1 gbDataHuC1;

// HuC1 ROM write registers
void mapperHuC1ROM(uint16 address, uint8 value)
{
  int tmpAddress = 0;

  switch(address & 0x6000) {
  case 0x0000: // RAM enable register
    gbDataHuC1.mapperRAMEnable = ( ( value & 0x0a) == 0x0a ? 1 : 0);
    break;

  case 0x2000: // ROM bank select
    value = value & 0x3f;
    if(value == 0)
      value = 1;
    if(value == gbDataHuC1.mapperROMBank)
      break;

    SetROMMap(value << 14);
    gbDataHuC1.mapperROMBank = value;
    break;

  case 0x4000: // RAM bank select
    if(gbDataHuC1.mapperMemoryModel == 1) {
      // 4/32 model, RAM bank switching provided
      value = value & 0x03;
      if(value == gbDataHuC1.mapperRAMBank)
        break;

      SetRAM8K(value);
      gbDataHuC1.mapperRAMBank = value;
    } else {
      // 16/8, set the high address
      gbDataHuC1.mapperROMHighAddress = value & 0x03;
      tmpAddress = gbDataHuC1.mapperROMBank << 14;
      tmpAddress |= (gbDataHuC1.mapperROMHighAddress) << 19;
      SetROMMap(tmpAddress);
    }
    break;

  case 0x6000: // memory model select
    gbDataHuC1.mapperMemoryModel = value & 1;
    break;
  }
}

// HuC1 RAM write
void mapperHuC1RAM(uint16 address, uint8 value)
{
  if(gbDataHuC1.mapperRAMEnable) {
    if(gbRamSize) {
      gbMemoryMap[address >> 12][address & 0x0fff] = value;
    }
  }
}

void memoryUpdateMapHuC1()
{
  SetROMMap(gbDataHuC1.mapperROMBank << 14);
  SetRAM8K(gbDataHuC1.mapperRAMBank);
}

mapperHuC3 gbDataHuC3;

// HuC3 ROM write registers
void mapperHuC3ROM(uint16 address, uint8 value)
{
  switch(address & 0x6000) {
  case 0x0000: // RAM enable register
    gbDataHuC3.mapperRAMEnable = ( value == 0x0a ? 1 : 0);
    gbDataHuC3.mapperRAMFlag = value;
    if(gbDataHuC3.mapperRAMFlag != 0x0a)
      gbDataHuC3.mapperRAMBank = -1;
    break;

  case 0x2000: // ROM bank select
    value = value & 0x7f;
    if(value == 0)
      value = 1;
    if(value == gbDataHuC3.mapperROMBank)
      break;

    gbDataHuC3.mapperROMBank = value;
    SetROMMap(value << 14);
    break;

  case 0x4000: // RAM bank select
    value = value & 0x03;
    if(value == gbDataHuC3.mapperRAMBank)
      break;

    gbDataHuC3.mapperRAMBank = value;
    SetRAM8K(value);
    break;

  case 0x6000: // nothing to do!
    break;
  }
}

// HuC3 read RAM
uint8 mapperHuC3ReadRAM(uint16 address)
{
  if(gbDataHuC3.mapperRAMFlag > 0x0b &&
     gbDataHuC3.mapperRAMFlag < 0x0e) {
    if(gbDataHuC3.mapperRAMFlag != 0x0c)
      return 1;
    return gbDataHuC3.mapperRAMValue;
  } else
    return gbMemoryMap[address >> 12][address & 0x0fff];
}

// HuC3 RAM write
void mapperHuC3RAM(uint16 address, uint8 value)
{
  int *p;

  if(gbDataHuC3.mapperRAMFlag < 0x0b ||
     gbDataHuC3.mapperRAMFlag > 0x0e) {
    if(gbDataHuC3.mapperRAMEnable) {
      if(gbRamSize) {
        gbMemoryMap[address >> 12][address & 0x0fff] = value;
      }
    }
  } else {
    if(gbDataHuC3.mapperRAMFlag == 0x0b) {
      if(value == 0x62) {
        gbDataHuC3.mapperRAMValue = 1;
      } else {
        switch(value & 0xf0) {
        case 0x10:
          p = &gbDataHuC3.mapperRegister2;
          gbDataHuC3.mapperRAMValue = *(p+gbDataHuC3.mapperRegister1++);
          if(gbDataHuC3.mapperRegister1 > 6)
            gbDataHuC3.mapperRegister1 = 0;
          break;
        case 0x30:
          p = &gbDataHuC3.mapperRegister2;
          *(p+gbDataHuC3.mapperRegister1++) = value & 0x0f;
          if(gbDataHuC3.mapperRegister1 > 6)
            gbDataHuC3.mapperRegister1 = 0;
          gbDataHuC3.mapperAddress =
            (gbDataHuC3.mapperRegister6 << 24) |
            (gbDataHuC3.mapperRegister5 << 16) |
            (gbDataHuC3.mapperRegister4 <<  8) |
            (gbDataHuC3.mapperRegister3 <<  4) |
            (gbDataHuC3.mapperRegister2);
          break;
        case 0x40:
          gbDataHuC3.mapperRegister1 = (gbDataHuC3.mapperRegister1 & 0xf0) |
            (value & 0x0f);
          gbDataHuC3.mapperRegister2 = (gbDataHuC3.mapperAddress & 0x0f);
          gbDataHuC3.mapperRegister3 = ((gbDataHuC3.mapperAddress>>4)&0x0f);
          gbDataHuC3.mapperRegister4 = ((gbDataHuC3.mapperAddress>>8)&0x0f);
          gbDataHuC3.mapperRegister5 = ((gbDataHuC3.mapperAddress>>16)&0x0f);
          gbDataHuC3.mapperRegister6 = ((gbDataHuC3.mapperAddress>>24)&0x0f);
          gbDataHuC3.mapperRegister7 = 0;
          gbDataHuC3.mapperRegister8 = 0;
          gbDataHuC3.mapperRAMValue = 0;
          break;
        case 0x50:
          gbDataHuC3.mapperRegister1 = (gbDataHuC3.mapperRegister1 & 0x0f) |
            ((value << 4)&0x0f);
          break;
        default:
          gbDataHuC3.mapperRAMValue = 1;
          break;
        }
      }
    }
  }
}

void memoryUpdateMapHuC3()
{
  SetROMMap(gbDataHuC3.mapperROMBank << 14);
  SetRAM8K(gbDataHuC3.mapperRAMBank);
}

}
