"use strict";

import { Platform, BasicZ80ScanlinePlatform } from "../baseplatform";
import { PLATFORMS, newAddressDecoder, padBytes, noise, makeKeycodeMap, Keys } from "../emu";
import { hex, lzgmini, stringToByteArray } from "../util";
import { MasterAudio, SN76489_Audio } from "../audio";
import { TMS9918A } from "../video/tms9918a";

// https://www.konamiman.com/msx/msx-e.html#msx2th
// https://www.msx.org/wiki/MSX_Cartridge_slot
// http://map.grauw.nl/resources/msx_io_ports.php
// https://openmsx.org/manual/setup.html
// https://www.msx.org/wiki/Slots
// https://www.msx.org/wiki/SDCC

var MSX_PRESETS = [
  {id:'helloworld.asm', name:'Hello World (ASM)'},
];

var MSX_KEYCODE_MAP = makeKeycodeMap([
  [Keys.VK_UP,    0, 0x1],
  [Keys.VK_DOWN,  0, 0x4],
  [Keys.VK_LEFT,  0, 0x8],
  [Keys.VK_RIGHT, 0, 0x2],
  [Keys.VK_SPACE, 0, 0x40],
  [Keys.VK_CONTROL, 1, 0x40],

  [Keys.VK_W, 2, 0x1],
  [Keys.VK_S, 2, 0x4],
  [Keys.VK_A, 2, 0x8],
  [Keys.VK_D, 2, 0x2],
  [Keys.VK_Z, 2, 0x40],
  [Keys.VK_X, 3, 0x40],
]);

/// standard emulator

interface MSXSlot {
  read(addr:number) : number;
  write(addr:number, val:number) : void;
}

class MSXPlatform extends BasicZ80ScanlinePlatform implements Platform {

  cpuFrequency = 3579545; // MHz
  canvasWidth = 304;
  numTotalScanlines = 262;
  numVisibleScanlines = 240;
  defaultROMSize = 0x10000;

  vdp : TMS9918A;
  bios : Uint8Array;
  slots : MSXSlot[];
  slotmask : number = 0;
  
  getPresets() { return MSX_PRESETS; }

  getKeyboardMap() { return MSX_KEYCODE_MAP; }
  
  getVideoOptions() { return {overscan:true}; }

  newRAM() {
    return new Uint8Array(0x10000);
  }
  
  newMembus() {
     // slot mapper
     return {
       read: (a) => {
         let shift = (a >> 14) << 1;
         let slotnum = (this.slotmask >> shift) & 3;
         let slot = this.slots[slotnum];
         return slot ? slot.read(a) : 0;
       },
       write: (a,v) => {
         let shift = (a >> 14) << 1;
         let slotnum = (this.slotmask >> shift) & 3;
         let slot = this.slots[slotnum];
         if (slot) slot.write(a, v);
       },
       isContended: () => { return false; },
    };
  }
  
  newIOBus() {
    return {
      read: (addr) => {
        addr &= 0xff;
        //console.log('IO read', hex(addr,4));
        switch (addr) {
          case 0x98: return this.vdp.readData();
          case 0x99: return this.vdp.readStatus();
          case 0xa8: return this.slotmask;
        }
        return 0;
      },
      write: (addr, val) => {
        addr &= 0xff;
        val &= 0xff;
        //console.log('IO write', hex(addr,4), hex(val,2));
        switch (addr) {
          case 0x98: this.vdp.writeData(val); break;
          case 0x99: this.vdp.writeAddress(val); break;
          case 0xa8: this.slotmask = val; break;
        }
      }
    };
  }

  start() {
    super.start();
    this.bios = new lzgmini().decode(stringToByteArray(atob(MSX1_BIOS_LZG)));
    this.slots = [
       // slot 0 : BIOS
       {
         read: (a) => { return this.bios[a] | 0; },
         write: (a,v) => { }
       },
       // slot 1: cartridge
       {
         read: (a) => { return this.rom[a] | 0; },
         write: (a,v) => { }
       },
       // slot 2 : empty
       null,
       // slot 3 : RAM
       {
         read: (a) => { return this.ram[a] | 0; },
         write: (a,v) => { this.ram[a] = v; }
       },
    ];
    this.audio = new MasterAudio();
    this.psg = new SN76489_Audio(this.audio);
    var cru = {
      setVDPInterrupt: (b) => {
        if (b) {
          this.cpu.nonMaskableInterrupt();
        } else {
          // TODO: reset interrupt?
        }
      }
    };
    this.vdp = this.newVDP(this.video.getFrameData(), cru, true); // true = 4 sprites/line
  }

  newVDP(frameData, cru, flicker) {
    return new TMS9918A(frameData, cru, flicker);
  }
  
  startScanline(sl : number) {
  }
  
  drawScanline(sl : number) {
    this.vdp.drawScanline(sl);
  }

  loadState(state) {
    super.loadState(state);
    this.vdp.restoreState(state['vdp']);
    this.slotmask = state['slotmask'];
  }
  saveState() {
    var state = super.saveState();
    state['vdp'] = this.vdp.getState();
    state['slotmask'] = this.slotmask;
    return state;
  }
  reset() {
    super.reset();
    this.vdp.reset();
    this.psg.reset();
    this.slotmask = 0;
  }

  getDebugCategories() {
    return super.getDebugCategories().concat(['VDP']);
  }
  getDebugInfo(category, state) {
    switch (category) {
      case 'VDP': return this.vdpStateToLongString(state.vdp);
      default: return super.getDebugInfo(category, state);
    }
  }
  vdpStateToLongString(ppu) {
    return this.vdp.getRegsString();
  }
}

///

PLATFORMS['msx'] = MSXPlatform;

///

var MSX1_BIOS_LZG = `
[encoded data, 12393 characters]
`;
