text: /**
   * NES
   * Copyright (C) 2021-2022 David Jolly
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
   * associated documentation files (the "Software"), to deal in the Software without restriction,
   * including without limitation the rights to use, copy, modify, merge, publish, distribute,
   * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all copies or
   * substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
   * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
   * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
   * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
   * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   */
  
  #include "../../include/system/input.h"
  #include "../../include/service.h"
  
  /**
   * key struct
   */
  typedef union {
  
      struct {
          uint8_t state : 1;          /* Key state (0:Released, 1:Pressed) */
          uint8_t state_extended : 1; /* Extended key state */
          uint8_t microphone : 1;     /* Microphone state */
          uint8_t unused : 5;         /* Unused */
      };
  
      uint8_t raw;
  } nes_key_t;
  
  /**
   * Strobe struct
   */
  typedef union {
  
      struct {
          uint8_t state : 1;          /* Strobe state */
          uint8_t state_extended : 2; /* Extended strobe state */
          uint8_t unused : 5;         /* Unused */
      };
  
      uint8_t raw;
  } nes_stobe_t;
  
  #ifdef __cplusplus
  extern "C" {
  #endif /* __cplusplus */
  
  int
  nes_input_initialize(
      __inout nes_input_t *input
      )
  {
  
      for(int controller = 0; controller < NES_CTRL_MAX; ++controller) {
          input->key[controller].position = NES_KEY_MAX;
      }
  
      return NES_SUCC;
  }
  
  uint8_t
  nes_input_read(
      __inout nes_input_t *input,
      __in uint16_t address
      )
  {
      int controller = NES_CTRL_MAX;
      nes_key_t result = { .state = 1, .state_extended = 0, .microphone = 0, .unused = 8 };
  
      switch(address) {
          case 0x4016:
              controller = NES_CTRL_0;
              break;
          case 0x4017:
              controller = NES_CTRL_1;
              break;
          default:
              break;
      }
  
      if(controller < NES_CTRL_MAX) {
  
          if(input->key[controller].position < NES_KEY_MAX) {
              result.state = input->key[controller].state[input->key[controller].position++] ? 1 : 0;
          }
      }
  
      return result.raw;
  }
  
  void
  nes_input_reset(
      __inout nes_input_t *input
      )
  {
  
      for(int controller = 0; controller < NES_CTRL_MAX; ++controller) {
  
          for(int key = 0; key < NES_KEY_MAX; ++key) {
              input->key[controller].state[key] = false;
          }
  
          input->key[controller].position = NES_KEY_MAX;
      }
  
      input->strobe = false;
  }
  
  void
  nes_input_uninitialize(
      __inout nes_input_t *input
      )
  {
      memset(input, 0, sizeof(*input));
  }
  
  void
  nes_input_write(
      __inout nes_input_t *input,
      __in uint16_t address,
      __in uint8_t data
      )
  {
      nes_stobe_t strobe = {};
  
      switch(address) {
          case 0x4016:
              strobe.raw = data;
  
              if(input->strobe) {
  
                  if(!strobe.state) {
  
                      for(int controller = 0; controller < NES_CTRL_MAX; ++controller) {
  
                          for(int key = 0; key < NES_KEY_MAX; ++key) {
                              input->key[controller].state[key] = nes_service_key(controller, key);
                          }
  
                          input->key[controller].position = NES_KEY_A;
                      }
                  }
              }
  
              input->strobe = strobe.state;
              break;
          default:
              break;
      }
  }
  
  #ifdef __cplusplus
  }
  #endif /* __cplusplus */
  