//Yamaha YM7101

struct VDP : Thread {
  static auto Enter() -> void;
  auto main() -> void;
  auto step(uint clocks) -> void;
  auto refresh() -> void;

  auto power(bool reset) -> void;

  //io.cpp
  auto read(uint24 addr) -> uint16;
  auto write(uint24 addr, uint16 data) -> void;

  auto readDataPort() -> uint16;
  auto writeDataPort(uint16 data) -> void;

  auto readControlPort() -> uint16;
  auto writeControlPort(uint16 data) -> void;

  struct DMA {
    //dma.cpp
    auto run() -> void;
    auto load() -> void;
    auto fill() -> void;
    auto copy() -> void;

    auto power() -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    struct IO {
      uint2  mode;
      uint22 source;
      uint16 length;
      uint8  fill;
      uint1  enable;
      uint1  wait;
    } io;
  } dma;

  //render.cpp
  auto scanline() -> void;
  auto run() -> void;
  auto outputPixel(uint32 color) -> void;

  struct Pixel {
    inline auto above() const -> bool { return priority == 1 && color; }
    inline auto below() const -> bool { return priority == 0 && color; }

    uint6 color;
    uint1 priority;
  };

  struct Background {
    enum class ID : uint { PlaneA, Window, PlaneB } id;

    //background.cpp
    auto isWindowed(uint x, uint y) -> bool;

    auto updateHorizontalScroll(uint y) -> void;
    auto updateVerticalScroll(uint x) -> void;

    auto nametableAddress() -> uint15;
    auto nametableWidth() -> uint;
    auto nametableHeight() -> uint;

    auto scanline(uint y) -> void;
    auto run(uint x, uint y) -> void;

    auto power() -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    struct IO {
      uint15 nametableAddress;

      //PlaneA, PlaneB
      uint2  nametableWidth;
      uint2  nametableHeight;
      uint15 horizontalScrollAddress;
      uint2  horizontalScrollMode;
      uint1  verticalScrollMode;

      //Window
      uint1  horizontalDirection;
      uint10 horizontalOffset;
      uint1  verticalDirection;
      uint10 verticalOffset;
    } io;

    struct State {
      uint10 horizontalScroll;
      uint10 verticalScroll;
    } state;

    Pixel output;
  };
  Background planeA{Background::ID::PlaneA};
  Background window{Background::ID::Window};
  Background planeB{Background::ID::PlaneB};

  struct Object {
    //sprite.cpp
    inline auto width() const -> uint;
    inline auto height() const -> uint;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    uint9  x;
    uint10 y;
    uint2  tileWidth;
    uint2  tileHeight;
    uint1  horizontalFlip;
    uint1  verticalFlip;
    uint2  palette;
    uint1  priority;
    uint11 address;
    uint7  link;
  };

  struct Sprite {
    //sprite.cpp
    auto write(uint9 addr, uint16 data) -> void;
    auto scanline(uint y) -> void;
    auto run(uint x, uint y) -> void;

    auto power() -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    struct IO {
      uint15 attributeAddress;
      uint1  nametableAddressBase;
    } io;

    Pixel output;

    array<Object, 80> oam;
    array<Object, 20> objects;
  };
  Sprite sprite;

  //serialization.cpp
  auto serialize(serializer&) -> void;

private:
  auto pixelWidth() const -> uint { return latch.displayWidth ? 4 : 5; }
  auto screenWidth() const -> uint { return latch.displayWidth ? 320 : 256; }
  auto screenHeight() const -> uint { return latch.overscan ? 240 : 224; }
  auto frameHeight() const -> uint { return Region::PAL() ? 312 : 262; }

  //video RAM
  struct VRAM {
    //memory.cpp
    auto read(uint15 address) const -> uint16;
    auto write(uint15 address, uint16 data) -> void;

    auto readByte(uint16 address) const -> uint8;
    auto writeByte(uint16 address, uint8 data) -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    uint16 memory[32768];
  } vram;

  //vertical scroll RAM
  struct VSRAM {
    //memory.cpp
    auto read(uint6 address) const -> uint10;
    auto write(uint6 address, uint10 data) -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    uint10 memory[40];
  } vsram;

  //color RAM
  struct CRAM {
    //memory.cpp
    auto read(uint6 address) const -> uint9;
    auto write(uint6 address, uint9 data) -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    uint9 memory[64];
  } cram;

  struct IO {
    //status
    uint1 vblankIRQ;  //true after VIRQ triggers; cleared at start of next frame

    //command
    uint6  command;
    uint16 address;
    uint1  commandPending;

    //$00  mode register 1
    uint1 displayOverlayEnable;
    uint1 counterLatch;
    uint1 horizontalBlankInterruptEnable;
    uint1 leftColumnBlank;

    //$01  mode register 2
    uint1 videoMode;  //0 = Master System; 1 = Mega Drive
    uint1 overscan;   //0 = 224 lines; 1 = 240 lines
    uint1 verticalBlankInterruptEnable;
    uint1 displayEnable;
    uint1 externalVRAM;

    //$07  background color
    uint6 backgroundColor;

    //$0a  horizontal interrupt counter
    uint8 horizontalInterruptCounter;

    //$0b  mode register 3
    uint1 externalInterruptEnable;

    //$0c  mode register 4
    uint2 displayWidth;
    uint2 interlaceMode;
    uint1 shadowHighlightEnable;
    uint1 externalColorEnable;
    uint1 horizontalSync;
    uint1 verticalSync;

    //$0e  nametable pattern base address
    uint1 nametableBasePatternA;
    uint1 nametableBasePatternB;

    //$0f  data port auto-increment value
    uint8 dataIncrement;
  } io;

  struct Latch {
    //per-frame
    uint1 overscan;
    uint8 horizontalInterruptCounter;

    //per-scanline
    uint2 displayWidth;
  } latch;

  struct State {
    uint32* output = nullptr;
    uint16 hdot;
    uint16 hcounter;
    uint16 vcounter;
    uint1 field;
  } state;

  uint32 buffer[1280 * 512];
  uint32* output = nullptr;

  friend class Interface;
};

extern VDP vdp;
