text: // Copyright 2021 Michael Rodriguez
  //
  // Permission to use, copy, modify, and/or distribute this software for any
  // purpose with or without fee is hereby granted, provided that the above
  // copyright notice and this permission notice appear in all copies.
  //
  // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
  // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  
  // This header file defines the interface all implementations must use. It
  // defines all of the necessary registers and methods necessary to carry out
  // the necessary actions.
  
  #ifndef C8_LIBC8_INTERFACE_H_
  #define C8_LIBC8_INTERFACE_H_
  
  #include <cstdint>
  
  #include "machine.h"
  
  class ImplementationInterface {
   public:
    virtual ~ImplementationInterface() noexcept {}
  
    c8::Word GetInstruction() const noexcept { return instruction_; }
    unsigned int GetProgramCounter() const noexcept { return program_counter_; }
    unsigned int GetI() const noexcept { return I_; }
    bool IsWaitingForKeyPress() const noexcept { return waiting_for_key_press_; }
  
    // Returns `true` if the program counter is valid and was set, or `false`
    // otherwise. A valid program counter is any integer in the range of
    // 0x000 (0)..0xFFD (4093).
    bool SetProgramCounter(const unsigned int new_program_counter) noexcept {
      return SetMemoryRegister(program_counter_, new_program_counter);
    }
  
    // Returns `true` if the address is valid and was set, or `false` otherwise. A
    // valid I register is any integer in the range of 0x000 (0)..0xFFD (4093).
    bool SetI(const unsigned int address) noexcept {
      return SetMemoryRegister(I_, address);
    }
  
    // Executes the next instruction, and returns the result of the step. See
    // the definition of `c8::StepResult` for more details.
    virtual c8::StepResult Step() noexcept = 0;
  
    // Resets the virtual machine to the startup state.
    virtual void Reset() noexcept {
      ClearRegisters();
      ClearStack();
      ClearFramebuffer();
      ClearRAM();
      CopyFontSetToRAM();
      InitializeRegisters();
    }
  
    // Updates the keypad.
    virtual void UpdateKeyState(const c8::Key key,
                                const c8::KeyState state) noexcept {
      if ((state == c8::KeyState::pressed) && waiting_for_key_press_) {
        V_[key_dest_] = key;
        waiting_for_key_press_ = false;
      }
      keypad_[key] = state;
    }
  
    // CHIP-8 contains 16 8-bit general purpose registers, commonly referred to as
    // `Vx`, where `x` is the register number between 0..15. The flag register
    // (15) is used by certain arithmetic instructions and should avoid being
    // written to by programs.
    std::array<c8::Byte, c8::data_size::kV> V_;
  
    // CHIP-8 contains ~4KB (4,096 bytes) of addressable storage space,
    // traditionally referred to as RAM. The regions of memory are as follows:
    //
    // [0x000 - 0x1FF] - Location where the original CHIP-8 intepreter resided.
    // This memory region may be used freely by modern programs, but be aware
    // that the first 80 bytes of this region will contain a font set. There's
    // nothing stopping programs from using this region for their own purposes if
    // the font set isn't required, however.
    //
    // In short: modern implementations don't restrict this memory region. The
    // COSMAC VIP probably would, though.
    //
    // [0x200 - 0xFFF] - This region contains the program code from which
    // instructions will initially be fetched. Any unused space by the program
    // code itself can be used by the program without restrictions.
    std::array<c8::Byte, c8::data_size::kRAM> ram_;
  
    // CHIP-8 contains a full-ascending call stack with a maximum depth of 16
    // subroutines.
    std::array<unsigned int, c8::data_size::kStack> stack_;
  
    // CHIP-8 contains a 64x32 monochrome framebuffer. While the original CHIP-8
    // drawing method XORd sprites onto the framebuffer, we simply store an RGB
    // value for white or black pixels allowing us to simply pass the framebuffer
    // to almost any rendering API as it provides context to what the data
    // actually represents.
    c8::Framebuffer framebuffer_;
  
    // While the sound timer is >0, a tone will play. This timer decrements by 1
    // at a rate of 60Hz, regardless of the execution speed of the virtual
    // machine. It can be between the values of 0..0xFF (255).
    c8::Byte sound_timer_;
  
    // The delay timer is used internally by programs and requires no special
    // action by the host, other than decrementing the timer by 1 every 60Hz. It
    // can be between the values of 0..0xFF (255).
    c8::Byte delay_timer_;
  
   protected:
    // If this is set to `true`, instruction execution will not take place until
    // the user presses a key.
    bool waiting_for_key_press_;
  
    // The register to store the pressed key in.
    unsigned int key_dest_;
  
    void ClearRegisters() noexcept {
      std::fill(V_.begin(), V_.end(), c8::initial_state::kV);
    }
  
    void ClearRAM() noexcept {
      std::fill(ram_.begin(), ram_.end(), c8::initial_state::kRAM);
    }
  
    void ClearStack() noexcept {
      std::fill(stack_.begin(), stack_.end(), c8::initial_state::kStack);
    }
  
    void ClearFramebuffer() noexcept {
      std::fill(framebuffer_.begin(), framebuffer_.end(), c8::pixel::kBlack);
    }
  
    void CopyFontSetToRAM() noexcept {
      constexpr std::array kFontSet = {
          0xF0, 0x90, 0x90, 0x90, 0xF0,  // 0
          0x20, 0x60, 0x20, 0x20, 0x70,  // 1
          0xF0, 0x10, 0xF0, 0x80, 0xF0,  // 2
          0xF0, 0x10, 0xF0, 0x10, 0xF0,  // 3
          0x90, 0x90, 0xF0, 0x10, 0x10,  // 4
          0xF0, 0x80, 0xF0, 0x10, 0xF0,  // 5
          0xF0, 0x80, 0xF0, 0x90, 0xF0,  // 6
          0xF0, 0x10, 0x20, 0x40, 0x40,  // 7
          0xF0, 0x90, 0xF0, 0x90, 0xF0,  // 8
          0xF0, 0x90, 0xF0, 0x10, 0xF0,  // 9
          0xF0, 0x90, 0xF0, 0x90, 0x90,  // A
          0xE0, 0x90, 0xE0, 0x90, 0xE0,  // B
          0xF0, 0x80, 0x80, 0x80, 0xF0,  // C
          0xE0, 0x90, 0x90, 0x90, 0xE0,  // D
          0xF0, 0x80, 0xF0, 0x80, 0xF0,  // E
          0xF0, 0x80, 0xF0, 0x80, 0x80   // F
      };
      std::copy(kFontSet.begin(), kFontSet.end(), ram_.begin());
    }
  
    void InitializeRegisters() noexcept {
      stack_pointer_ = c8::initial_state::kStackPointer;
      program_counter_ = c8::initial_state::kProgramCounter;
      delay_timer_ = c8::initial_state::kDelayTimer;
      sound_timer_ = c8::initial_state::kSoundTimer;
      I_ = c8::initial_state::kI;
      waiting_for_key_press_ = false;
      keypad_.fill(false);
    }
  
    std::array<bool, c8::data_size::kKeypad> keypad_;
  
    // The instruction currently being processed. I explicitly didn't call this
    // the current instruction in code as it implies we store a past or future
    // instruction.
    c8::Word instruction_;
  
    // The program counter is the index in memory where instructions are to be
    // fetched. While the program counter on paper should be 16-bit,
    unsigned int program_counter_;
  
    // The I register stores a 12-bit memory address to be used as a destination
    // or source in/from memory for register values.
    unsigned int I_;
  
    // The stack pointer is the index to the call stack. It has a range between
    // 0..15. While on paper the stack pointer can be 8-bit, in practice it
    // doesn't need to be.
    size_t stack_pointer_;
  
    // The constructor is declared private to signify this is an abstract class.
    ImplementationInterface() noexcept {}
  
   private:
    // Performs bounds checking on `value` against the maximum program counter.
    // Returns `true` if the value is within range and sets the destination to the
    // value, or `false` otherwise.
    bool SetMemoryRegister(unsigned int& dest,
                           const unsigned int value) noexcept {
      if (value > c8::limits::kProgramCounter) {
        return false;
      }
      dest = value;
      return true;
    }
  };
  
  #endif  // C8_LIBC8_INTERFACE_H_
  