
// ep128emu -- portable Enterprise 128 emulator
// Copyright (C) 2003-2017 Istvan Varga <istvanv@users.sourceforge.net>
// https://github.com/istvan-v/ep128emu/
//
// 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 of the License, 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

#ifndef EP128EMU_EP128VM_HPP
#define EP128EMU_EP128VM_HPP

#include "ep128emu.hpp"
#include "z80/z80.hpp"
#include "memory.hpp"
#include "ioports.hpp"
#include "dave.hpp"
#include "nick.hpp"
#include "display.hpp"
#include "snd_conv.hpp"
#include "soundio.hpp"
#include "vm.hpp"
#include "ep_fdd.hpp"
#include "wd177x.hpp"
#ifdef ENABLE_SDEXT
#  include "sdext.hpp"
#endif
#ifdef ENABLE_RESID
#  include "resid/sid.hpp"
#endif

#include <map>

namespace Ep128Emu {
  class VideoCapture;
}

namespace Ep128 {

  class IDEInterface;

  class Ep128VM : public Ep128Emu::VirtualMachine {
   private:
    class Z80_ : public Z80 {
     private:
      Ep128VM&  vm;
      std::map< uint8_t, std::FILE * >  fileChannels;
      bool      defaultDeviceIsFILE;
     public:
      Z80_(Ep128VM& vm_);
      virtual ~Z80_();
     protected:
      virtual EP128EMU_REGPARM1 void executeInterrupt();
      virtual EP128EMU_REGPARM2 uint8_t readMemory(uint16_t addr);
      virtual EP128EMU_REGPARM2 uint16_t readMemoryWord(uint16_t addr);
      virtual EP128EMU_REGPARM1 uint8_t readOpcodeFirstByte();
      virtual EP128EMU_REGPARM2
          uint8_t readOpcodeSecondByte(const bool *invalidOpcodeTable =
                                           (bool *) 0);
      virtual EP128EMU_REGPARM2 uint8_t readOpcodeByte(int offset);
      virtual EP128EMU_REGPARM2 uint16_t readOpcodeWord(int offset);
      virtual EP128EMU_REGPARM3 void writeMemory(uint16_t addr, uint8_t value);
      virtual EP128EMU_REGPARM3 void writeMemoryWord(uint16_t addr,
                                                     uint16_t value);
      virtual EP128EMU_REGPARM2 void pushWord(uint16_t value);
      virtual EP128EMU_REGPARM3 void doOut(uint16_t addr, uint8_t value);
      virtual EP128EMU_REGPARM2 uint8_t doIn(uint16_t addr);
      virtual EP128EMU_REGPARM1 void updateCycle();
      virtual EP128EMU_REGPARM2 void updateCycles(int cycles);
      virtual EP128EMU_REGPARM1 void tapePatch();
     private:
      uint8_t readUserMemory(uint16_t addr);
      void writeUserMemory(uint16_t addr, uint8_t value);
     public:
      void closeAllFiles();
    };
    class Memory_ : public Memory {
     private:
      Ep128VM&  vm;
     public:
      Memory_(Ep128VM& vm_);
      virtual ~Memory_();
     protected:
      virtual void breakPointCallback(bool isWrite,
                                      uint16_t addr, uint8_t value);
    };
    class IOPorts_ : public IOPorts {
     private:
      Ep128VM&  vm;
     public:
      IOPorts_(Ep128VM& vm_);
      virtual ~IOPorts_();
     protected:
      virtual void breakPointCallback(bool isWrite,
                                      uint16_t addr, uint8_t value);
    };
    class Dave_ : public Dave {
     private:
      Ep128VM&  vm;
     public:
      Dave_(Ep128VM& vm_);
      virtual ~Dave_();
     protected:
      virtual void setMemoryPage(uint8_t page, uint8_t segment);
      virtual void setMemoryWaitMode(int mode);
      virtual void setRemote1State(int state);
      virtual void setRemote2State(int state);
      virtual void interruptRequest();
      virtual void clearInterruptRequest();
    };
    class Nick_ : public Nick {
     private:
      Ep128VM&  vm;
     public:
      Nick_(Ep128VM& vm_);
      virtual ~Nick_();
     protected:
      virtual void irqStateChange(bool newState);
      virtual void drawLine(const uint8_t *buf, size_t nBytes);
      virtual void vsyncStateChange(bool newState, unsigned int currentSlot_);
    };
    // ----------------
    Z80_      z80;
    Memory_   memory;
    IOPorts_  ioPorts;
    Dave_     dave;
    Nick_     nick;
    uint8_t   pageTable[4];
    uint32_t  nickCyclesRemainingL;     // in 2^-32 NICK cycle units
    int32_t   nickCyclesRemainingH;
    int64_t   cpuCyclesPerNickCycle;    // in 2^-32 Z80 cycle units
    int64_t   cpuCyclesRemaining;       // in 2^-32 Z80 cycle units
    int64_t   daveCyclesPerNickCycle;   // in 2^-32 DAVE cycle units
    int64_t   daveCyclesRemaining;      // in 2^-32 DAVE cycle units
    int64_t   memoryWaitCycles_M1;      // in 2^-32 Z80 cycle units
    int64_t   memoryWaitCycles;         // in 2^-32 Z80 cycle units
    uint8_t   memoryWaitMode;           // set on write to port 0xBF
    bool      memoryTimingEnabled;
    // 0: normal mode, 1: single step, 2: step over, 3: trace
    uint8_t   singleStepMode;
    int32_t   singleStepModeNextAddr;
    bool      tapeCallbackFlag;
    // bit 0: 1 if remote 1 is on
    // bit 1: 1 if remote 2 is on
    uint8_t   remoteControlState;
    uint32_t  soundOutputSignal;
    uint32_t  externalDACOutput;
    Ep128Emu::File  *demoFile;
    // contains demo data, which is the emulator version number as a 32-bit
    // integer ((MAJOR << 16) + (MINOR << 8) + PATCHLEVEL), followed by a
    // sequence of events in the following format:
    //   uint64_t   deltaTime   (in NICK cycles, stored as MSB first dynamic
    //                          length (1 to 8 bytes) value)
    //   uint8_t    eventType   (currently allowed values are 0 for end of
    //                          demo (zero data bytes), 1 for key press,
    //                          2 for key release, and 3 for mouse event)
    //   uint8_t    dataLength  number of event data bytes
    //   ...        eventData   (dataLength bytes)
    // the event data for event types 1 and 2 is the key code with a length of
    // one byte:
    //   uint8_t    keyCode     key code in the range 0 to 127
    Ep128Emu::File::Buffer  demoBuffer;
    // true while recording a demo
    bool      isRecordingDemo;
    // true while playing a demo
    bool      isPlayingDemo;
    // true after loading a snapshot; if not playing a demo as well, the
    // keyboard state will be cleared
    bool      snapshotLoadFlag;
    // used for counting time between demo events (in NICK cycles)
    uint64_t  demoTimeCnt;
    // floppy drives
    Ep128Emu::WD177x      wd177x;
    Ep128Emu::FloppyDrive floppyDrives[4];
    uint8_t   breakPointPriorityThreshold;
    uint8_t   cmosMemoryRegisterSelect;
    bool      spectrumEmulatorEnabled;
    uint8_t   spectrumEmulatorIOPorts[4];
    uint8_t   cmosMemory[64];
    int64_t   prvRTCTime;
    struct Ep128VMCallback {
      void      (*func)(void *);
      void      *userData;
      Ep128VMCallback *nxt;
    };
    Ep128VMCallback   callbacks[16];
    Ep128VMCallback   *firstCallback;
    Ep128Emu::VideoCapture  *videoCapture;
    uint8_t   externalDACIOPorts[4];
    uint32_t  nickCyclesPerCPUCycleD2;  // in 2^-31 NICK cycle units
    uint32_t  videoMemoryWaitMult;      // (Z80 freq / NICK freq) * 16384
    uint32_t  videoMemoryWaitCycles;    // in 2^-18 NICK cycle units
    uint32_t  videoMemoryWaitCycles_M1; //            -"-
    uint32_t  videoMemoryWaitCycles_IO; //            -"-
    int64_t   tapeSamplesPerNickCycle;
    int64_t   tapeSamplesRemaining;
    size_t    cpuFrequency;             // defaults to 4000000 Hz
    size_t    daveFrequency;            // defaults to 500000 Hz
    size_t    nickFrequency;            // defaults to 889846 Hz
    int       waitCycleCnt;             // 1 for Z80 <= 5000000 Hz, 2 otherwise
    int       videoMemoryLatency;       // in picoseconds
    int       videoMemoryLatency_M1;    //      -"-
    int       videoMemoryLatency_IO;    //      -"-
    IDEInterface  *ideInterface;
    bool      mouseEmulationEnabled;    // cleared on reset, set on RTS toggle
    uint8_t   prvB7PortState;
    uint32_t  mouseTimer;               // NICK slots, counts down from 1500 us
    uint64_t  mouseData;                // data buffer (b60..b63 = next nibble)
    int8_t    mouseDeltaX;
    int8_t    mouseDeltaY;
    uint8_t   mouseButtonState;
    uint8_t   mouseWheelDelta;          // b0..b3: vertical, b4..b7: horizontal
#ifdef ENABLE_SDEXT
    SDExt     sdext;
#endif
#ifdef ENABLE_RESID
    SID       *sid;
    bool      sidEnabled;
    uint8_t   sidModel;                 // 0: disabled, 1: 6581, 2: 8580
    uint8_t   sidAddressRegister;
    int32_t   sidOutputAccumulator;
    int32_t   sidVolumeL;
    int32_t   sidVolumeR;
#endif
#ifdef ENABLE_MIDI_PORT
    Ep128Emu::Mutex midiBufferMutex;
    uint8_t   midiBufferReadPos;
    uint8_t   midiBufferWritePos;
    uint8_t   midiDevFlags;
    uint8_t   midiSavedStatus;
    uint8_t   midiBuffer[256];
#endif
    // ----------------
    void updateTimingParameters();
    void setMemoryWaitTiming();
    inline void updateCPUCycles(int cycles);
    EP128EMU_REGPARM1 void videoMemoryWait();
    EP128EMU_REGPARM1 void videoMemoryWait_M1();
    EP128EMU_REGPARM1 void videoMemoryWait_IO();
    // called from the Z80 emulation to synchronize NICK and DAVE with the CPU
    EP128EMU_REGPARM1 void runDevices();
    static uint8_t davePortReadCallback(void *userData, uint16_t addr);
    static void davePortWriteCallback(void *userData,
                                      uint16_t addr, uint8_t value);
    static uint8_t nickPortReadCallback(void *userData, uint16_t addr);
    static void nickPortWriteCallback(void *userData,
                                      uint16_t addr, uint8_t value);
    static uint8_t nickPortDebugReadCallback(void *userData, uint16_t addr);
    static uint8_t exdosPortReadCallback(void *userData, uint16_t addr);
    static void exdosPortWriteCallback(void *userData,
                                       uint16_t addr, uint8_t value);
    static uint8_t exdosPortDebugReadCallback(void *userData, uint16_t addr);
    static uint8_t spectrumEmulatorIOReadCallback(void *userData,
                                                  uint16_t addr);
    static void spectrumEmulatorIOWriteCallback(void *userData,
                                                uint16_t addr, uint8_t value);
    static uint8_t externalDACIOReadCallback(void *userData, uint16_t addr);
    static void externalDACIOWriteCallback(void *userData,
                                           uint16_t addr, uint8_t value);
#ifdef ENABLE_MIDI_PORT
    static uint8_t midiPortReadCallback(void *userData, uint16_t addr);
    static void midiPortWriteCallback(void *userData,
                                      uint16_t addr, uint8_t value);
    static uint8_t midiPortDebugReadCallback(void *userData, uint16_t addr);
#endif
    static uint8_t cmosMemoryIOReadCallback(void *userData, uint16_t addr);
    static void cmosMemoryIOWriteCallback(void *userData,
                                          uint16_t addr, uint8_t value);
    static uint8_t ideDriveIOReadCallback(void *userData, uint16_t addr);
    static void ideDriveIOWriteCallback(void *userData,
                                        uint16_t addr, uint8_t value);
    static void mouseRTSWriteCallback(void *userData,
                                      uint16_t addr, uint8_t value);
#ifdef ENABLE_RESID
    static uint8_t sidPortReadCallback(void *userData, uint16_t addr);
    static void sidPortWriteCallback(void *userData,
                                     uint16_t addr, uint8_t value);
    static uint8_t sidPortDebugReadCallback(void *userData, uint16_t addr);
#endif
    static void mouseTimerCallback(void *userData);
    static void tapeCallback(void *userData);
    static void demoPlayCallback(void *userData);
    static void demoRecordCallback(void *userData);
    static void videoCaptureCallback(void *userData);
#ifdef ENABLE_RESID
    static void sidCallback(void *userData);
#endif
    void stopDemoPlayback();
    void stopDemoRecording(bool writeFile_);
    uint8_t checkSingleStepModeBreak();
    void spectrumEmulatorNMI_AttrWrite(uint32_t addr, uint8_t value);
    void updateRTC();
    void resetCMOSMemory();
    void resetFloppyDrives(bool isColdReset);
    // Set function to be called at every NICK cycle. The functions are called
    // in the order of being registered; up to 16 callbacks can be set.
    void setCallback(void (*func)(void *userData), void *userData_,
                     bool isEnabled);
   public:
    Ep128VM(Ep128Emu::VideoDisplay&, Ep128Emu::AudioOutput&);
    virtual ~Ep128VM();
    /*!
     * Run emulation for the specified number of microseconds.
     */
    virtual void run(size_t microseconds);
    /*!
     * Reset emulated machine; if 'isColdReset' is true, RAM is cleared.
     */
    virtual void reset(bool isColdReset = false);
    /*!
     * Delete all ROM segments, and resize RAM to 'memSize' kilobytes;
     * implies calling reset(true).
     */
    virtual void resetMemoryConfiguration(size_t memSize);
    /*!
     * Load ROM segment 'n' from the specified file, skipping 'offs' bytes.
     */
    virtual void loadROMSegment(uint8_t n, const char *fileName, size_t offs);
    /*!
     * Load epmemcfg format memory configuration file.
     */
    virtual void loadMemoryConfiguration(const std::string& fileName_);
#ifdef ENABLE_SDEXT
    /*!
     * Set if SD card emulation should be enabled on segment 0x07,
     * and the file name of the 64 KB flash ROM image.
     */
    virtual void configureSDCard(bool isEnabled,
                                 const std::string& romFileName);
#endif
#ifdef ENABLE_RESID
    /*!
     * Configure SID 'n' (0 to 3, currently only 3 is supported),
     * 'model' can be 0 to disable the emulation, 1 for MOS 6581 or 2 for 8580.
     */
    virtual void setSIDConfiguration(int n, int model,
                                     double volumeL, double volumeR);
#endif
    /*!
     * Set CPU clock frequency (in Hz); defaults to 4000000 Hz.
     */
    virtual void setCPUFrequency(size_t freq_);
    /*!
     * Set the number of video 'slots' per second (defaults to 889846 Hz).
     */
    virtual void setVideoFrequency(size_t freq_);
    /*!
     * Set DAVE sample rate (defaults to 500000 Hz).
     */
    virtual void setSoundClockFrequency(size_t freq_);
    /*!
     * Set if emulation of memory timing is enabled.
     */
    virtual void setEnableMemoryTimingEmulation(bool isEnabled);
    /*!
     * Set state of key 'keyCode' (0 to 127; see dave.hpp).
     */
    virtual void setKeyboardState(int keyCode, bool isPressed);
    /*!
     * Send mouse event to the emulated machine. 'dX' and 'dY' are the
     * horizontal and vertical motion of the pointer relative to the position
     * at the time of the previous call, positive values move to the left and
     * up, respectively.
     * Each bit of 'buttonState' corresponds to the current state of a mouse
     * button (1 = pressed):
     *   b0 = left button
     *   b1 = right button
     *   b2 = middle button
     *   b3..b7 = buttons 4 to 8
     * 'mouseWheelEvents' can be the sum of any of the following:
     *   1: mouse wheel up
     *   2: mouse wheel down
     *   4: mouse wheel left
     *   8: mouse wheel right
     */
    virtual void setMouseState(int8_t dX, int8_t dY,
                               uint8_t buttonState, uint8_t mouseWheelEvents);
    /*!
     * Returns status information about the emulated machine (see also
     * struct VMStatus above, and the comments for functions that return
     * individual status values).
     */
    virtual void getVMStatus(VirtualMachine::VMStatus& vmStatus_);
    /*!
     * Create video capture object with the specified frame rate (24 to 60)
     * and format (768x576 RLE8 or 384x288 YV12) if it does not exist yet,
     * and optionally set callbacks for printing error messages and asking
     * for a new output file on reaching 2 GB file size.
     */
    virtual void openVideoCapture(
        int frameRate_ = 50,
        bool yuvFormat_ = false,
        void (*errorCallback_)(void *userData, const char *msg) =
            (void (*)(void *, const char *)) 0,
        void (*fileNameCallback_)(void *userData, std::string& fileName) =
            (void (*)(void *, std::string&)) 0,
        void *userData_ = (void *) 0);
    /*!
     * Set output file name for video capture (an empty file name means no
     * file is written). openVideoCapture() should be called first.
     */
    virtual void setVideoCaptureFile(const std::string& fileName_);
    /*!
     * Destroy video capture object, freeing all allocated memory and closing
     * the output file.
     */
    virtual void closeVideoCapture();
#ifdef ENABLE_MIDI_PORT
    /*!
     * Send MIDI event to the emulated machine
     * (evt = status + (data1 << 8) + (data2 << 16).
     */
    virtual void midiInReceiveEvent(int32_t evt);
    /*!
     * Receive MIDI event sent by the emulated machine, in the format
     * status + (data1 << 8) + (data2 << 16). If no event is available,
     * -1 is returned.
     */
    virtual int32_t midiOutSendEvent();
    /*!
     * Set MIDI device type: 0 = none (default), 1 = input, 2 = output.
     */
    virtual void midiSetDeviceType(int t);
#endif
    // -------------------------- DISK AND FILE I/O ---------------------------
    /*!
     * Load disk image for drive 'n' (counting from zero; 0 to 3 are floppy
     * drives, and 4 to 7 are IDE drives); an empty file name means no disk.
     */
    virtual void setDiskImageFile(int n, const std::string& fileName_,
                                  int nTracks_ = -1, int nSides_ = 2,
                                  int nSectorsPerTrack_ = 9);
    /*!
     * Returns the current state of the disk drive LEDs, which is the sum
     * of any of the following values:
     *   0x00000001: floppy drive 0 red LED is on
     *   0x00000002: floppy drive 0 green LED is on
     *   0x00000004: IDE drive 0 red LED is on (low priority)
     *   0x0000000C: IDE drive 0 red LED is on (high priority)
     *   0x00000100: floppy drive 1 red LED is on
     *   0x00000200: floppy drive 1 green LED is on
     *   0x00000400: IDE drive 1 red LED is on (low priority)
     *   0x00000C00: IDE drive 1 red LED is on (high priority)
     *   0x00010000: floppy drive 2 red LED is on
     *   0x00020000: floppy drive 2 green LED is on
     *   0x00040000: IDE drive 2 red LED is on (low priority)
     *   0x000C0000: IDE drive 2 red LED is on (high priority)
     *   0x00100000: SD card 1 blue LED is on (low priority)
     *   0x00200000: SD card 1 blue LED is on (high priority)
     *   0x00300000: SD card 1 cyan LED is on (high priority)
     *   0x01000000: floppy drive 3 red LED is on
     *   0x02000000: floppy drive 3 green LED is on
     *   0x04000000: IDE drive 3 red LED is on (low priority)
     *   0x0C000000: IDE drive 3 red LED is on (high priority)
     *   0x10000000: SD card 2 blue LED is on (low priority)
     *   0x20000000: SD card 2 blue LED is on (high priority)
     *   0x30000000: SD card 2 cyan LED is on (high priority)
     */
    virtual uint32_t getFloppyDriveLEDState();
    // ---------------------------- TAPE EMULATION ----------------------------
    /*!
     * Set tape image file name (if the file name is NULL or empty, tape
     * emulation is disabled).
     */
    virtual void setTapeFileName(const std::string& fileName);
    /*!
     * Start tape playback.
     */
    virtual void tapePlay();
    /*!
     * Start tape recording; if the tape file is read-only, this is
     * equivalent to calling tapePlay().
     */
    virtual void tapeRecord();
    // ------------------------------ DEBUGGING -------------------------------
    /*!
     * Add or delete a single breakpoint (see also bplist.hpp).
     */
    virtual void setBreakPoint(const Ep128Emu::BreakPoint& bp,
                               bool isEnabled = true);
    /*!
     * Clear all breakpoints.
     */
    virtual void clearBreakPoints();
    /*!
     * Set breakpoint priority threshold (0 to 4); breakpoints with a
     * priority less than this value will not trigger a break.
     */
    virtual void setBreakPointPriorityThreshold(int n);
    /*!
     * Set if the breakpoint callback should be called whenever the first byte
     * of a CPU instruction is read from memory. 'mode_' should be one of the
     * following values:
     *   0: normal mode
     *   1: single step mode (break on every instruction, ignore breakpoints)
     *   2: step over mode
     *   3: trace (similar to mode 1, but does not ignore breakpoints)
     *   4: step into mode
     */
    virtual void setSingleStepMode(int mode_);
    /*!
     * Set the next address where single step mode will stop, ignoring any
     * other instructions. If 'addr' is negative, then a break is triggered
     * immediately at the next instruction.
     * Note: setSingleStepMode() must be called first with a mode parameter
     * of 2 or 4.
     */
    virtual void setSingleStepModeNextAddress(int32_t addr);
    /*!
     * Returns the segment at page 'n' (0 to 3).
     */
    virtual uint8_t getMemoryPage(int n) const;
    /*!
     * Read a byte from memory. If 'isCPUAddress' is false, bits 14 to 21 of
     * 'addr' define the segment number, while bits 0 to 13 are the offset
     * (0 to 0x3FFF) within the segment; otherwise, 'addr' is interpreted as
     * a 16-bit CPU address.
     */
    virtual uint8_t readMemory(uint32_t addr, bool isCPUAddress = false) const;
    /*!
     * Write a byte to memory. If 'isCPUAddress' is false, bits 14 to 21 of
     * 'addr' define the segment number, while bits 0 to 13 are the offset
     * (0 to 0x3FFF) within the segment; otherwise, 'addr' is interpreted as
     * a 16-bit CPU address.
     * NOTE: calling this function will stop any demo recording or playback.
     */
    virtual void writeMemory(uint32_t addr, uint8_t value,
                             bool isCPUAddress = false);
    /*!
     * Write a byte to any memory (RAM or ROM). Bits 14 to 21 of 'addr' define
     * the segment number, while bits 0 to 13 are the offset (0 to 0x3FFF)
     * within the segment.
     * NOTE: calling this function will stop any demo recording or playback.
     */
    virtual void writeROM(uint32_t addr, uint8_t value);
    /*!
     * Read a byte from I/O port 'addr'.
     */
    virtual uint8_t readIOPort(uint16_t addr) const;
    /*!
     * Write a byte to I/O port 'addr'.
     * NOTE: calling this function will stop any demo recording or playback.
     */
    virtual void writeIOPort(uint16_t addr, uint8_t value);
    /*!
     * Returns the current value of the Z80 program counter (PC).
     */
    virtual uint16_t getProgramCounter() const;
    /*!
     * Set the CPU program counter (PC) to a new address. The change will only
     * take effect after the completion of one instruction.
     * NOTE: calling this function may stop any demo recording or playback.
     */
    virtual void setProgramCounter(uint16_t addr);
    /*!
     * Returns the CPU address of the last byte pushed to the stack.
     */
    virtual uint16_t getStackPointer() const;
    /*!
     * Dumps the current values of all CPU registers to 'buf' in ASCII format.
     * The register list may be written as multiple lines separated by '\n'
     * characters, however, there is no newline character at the end of the
     * buffer. The maximum line width is 56 characters.
     */
    virtual void listCPURegisters(std::string& buf) const;
    /*!
     * Dumps the current values of all I/O registers to 'buf' in ASCII format.
     * The register list may be written as multiple lines separated by '\n'
     * characters, however, there is no newline character at the end of the
     * buffer. The maximum line width is 40 characters.
     */
    virtual void listIORegisters(std::string& buf) const;
    /*!
     * Disassemble one Z80 instruction, starting from memory address 'addr',
     * and write the result to 'buf' (not including a newline character).
     * 'offs' is added to the instruction address that is printed.
     * The maximum line width is 40 characters.
     * Returns the address of the next instruction. If 'isCPUAddress' is
     * true, 'addr' is interpreted as a 16-bit CPU address, otherwise it
     * is assumed to be a 22-bit physical address (8 bit segment + 14 bit
     * offset).
     */
    virtual uint32_t disassembleInstruction(std::string& buf, uint32_t addr,
                                            bool isCPUAddress = false,
                                            int32_t offs = 0) const;
    /*!
     * Returns a reference to a structure containing all Z80 registers;
     * see z80/z80.hpp for more information.
     * NOTE: getting a non-const reference will stop any demo recording or
     * playback.
     */
    virtual Z80_REGISTERS& getZ80Registers();
    virtual const Z80_REGISTERS& getZ80Registers() const;
    /*!
     * Returns the current horizontal (0 to 56) and vertical (0 to 0xFFFFF)
     * video position. The vertical position is the sum of the LPB line
     * counter, and the LPB video memory address multiplied by 16.
     */
    virtual void getVideoPosition(int& xPos, int& yPos) const;
    // ------------------------------- FILE I/O -------------------------------
    /*!
     * Save snapshot of virtual machine state, including all ROM and RAM
     * segments, as well as all hardware registers. Note that the clock
     * frequency and timing settings, tape and disk state, and breakpoint list
     * are not saved.
     */
    virtual void saveState(Ep128Emu::File&);
    /*!
     * Save clock frequency and timing settings.
     */
    virtual void saveMachineConfiguration(Ep128Emu::File&);
    /*!
     * Register all types of file data supported by this class, for use by
     * Ep128Emu::File::processAllChunks(). Note that loading snapshot data
     * will clear all breakpoints.
     */
    virtual void registerChunkTypes(Ep128Emu::File&);
    /*!
     * Start recording a demo to the file object, which will be used until
     * the recording is stopped for some reason.
     * Implies calling saveMachineConfiguration() and saveState() first.
     */
    virtual void recordDemo(Ep128Emu::File&);
    /*!
     * Stop playing or recording demo.
     */
    virtual void stopDemo();
    /*!
     * Returns true if a demo is currently being recorded. The recording stops
     * when stopDemo() is called, any tape or disk I/O is attempted, clock
     * frequency and timing settings are changed, or a snapshot is loaded.
     * This function will also flush demo data to the associated file object
     * after recording is stopped for some reason other than calling
     * stopDemo().
     */
    virtual bool getIsRecordingDemo();
    /*!
     * Returns true if a demo is currently being played. The playback stops
     * when the end of the demo is reached, stopDemo() is called, any tape or
     * disk I/O is attempted, clock frequency and timing settings are changed,
     * or a snapshot is loaded. Note that keyboard events are ignored while
     * playing a demo.
     */
    virtual bool getIsPlayingDemo() const;
    // ----------------
    virtual void loadState(Ep128Emu::File::Buffer&);
    virtual void loadMachineConfiguration(Ep128Emu::File::Buffer&);
    virtual void loadDemo(Ep128Emu::File::Buffer&);
  };

}       // namespace Ep128

#endif  // EP128EMU_EP128VM_HPP

