/***************************************************************************************
 *  Genesis Plus
 *  I/O controller (Genesis & Master System modes)
 *
 *  Support for Master System (315-5216, 315-5237 & 315-5297), Game Gear & Mega Drive I/O chips
 *
 *  Copyright (C) 1998-2003  Charles Mac Donald (original code)
 *  Copyright (C) 2007-2026  Eke-Eke (Genesis Plus GX)
 *
 *  Redistribution and use of this code or any derivative works are permitted
 *  provided that the following conditions are met:
 *
 *   - Redistributions may not be sold, nor may they be used in a commercial
 *     product or activity.
 *
 *   - Redistributions that are modified from the original source must include the
 *     complete source code, including the source code for all components used by a
 *     binary built from the modified sources. However, as a special exception, the
 *     source code distributed need not include anything that is normally distributed
 *     (in either source or binary form) with the major components (compiler, kernel,
 *     and so on) of the operating system on which the executable runs, unless that
 *     component itself accompanies the executable.
 *
 *   - Redistributions must reproduce the above copyright notice, this list of
 *     conditions and the following disclaimer in the documentation and/or other
 *     materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************************/

#include "shared.h"
#include "gamepad.h"
#include "lightgun.h"
#include "mouse.h"
#include "activator.h"
#include "xe_1ap.h"
#include "teamplayer.h"
#include "paddle.h"
#include "sportspad.h"
#include "graphic_board.h"
#include "smash.h"

uint8 io_reg[0x10];

uint8 region_code = REGION_USA;

static struct port_t
{
  void (*data_w)(unsigned char data, unsigned char mask);
  unsigned char (*data_r)(void);
} port[3];

static void dummy_write(unsigned char data, unsigned char mask)
{
}

static unsigned char dummy_read(void)
{
  return 0x7F;
}

/*****************************************************************************
 * I/O chip initialization                                                   *
 *                                                                           *
 *****************************************************************************/
void io_init(void)
{
  /* Initialize connected peripherals */
  input_init();

  /* Initialize IO Ports handlers & connected peripherals */
  switch (input.system[0])
  {
    case SYSTEM_GAMEPAD:
    {
      port[0].data_w = (input.dev[0] == DEVICE_PAD2B) ? dummy_write : gamepad_1_write;
      port[0].data_r = gamepad_1_read;
      break;
    }

    case SYSTEM_MOUSE:
    {
      port[0].data_w = mouse_write;
      port[0].data_r = mouse_read;
      break;
    }

    case SYSTEM_ACTIVATOR:
    {
      port[0].data_w = activator_1_write;
      port[0].data_r = activator_1_read;
      break;
    }

    case SYSTEM_XE_1AP:
    {
      port[0].data_w = xe_1ap_1_write;
      port[0].data_r = xe_1ap_1_read;
      break;
    }

    case SYSTEM_WAYPLAY:
    {
      port[0].data_w = wayplay_1_write;
      port[0].data_r = wayplay_1_read;
      break;
    }

    case SYSTEM_TEAMPLAYER:
    {
      port[0].data_w = teamplayer_1_write;
      port[0].data_r = teamplayer_1_read;
      break;
    }

    case SYSTEM_MASTERTAP:
    {
      port[0].data_w = mastertap_1_write;
      port[0].data_r = mastertap_1_read;
      break;
    }

    case SYSTEM_LIGHTPHASER:
    {
      port[0].data_w = dummy_write;
      port[0].data_r = phaser_1_read;
      break;
    }

    case SYSTEM_PADDLE:
    {
      port[0].data_w = paddle_1_write;
      port[0].data_r = paddle_1_read;
      break;
    }

    case SYSTEM_SPORTSPAD:
    {
      port[0].data_w = sportspad_1_write;
      port[0].data_r = sportspad_1_read;
      break;
    }

    case SYSTEM_GRAPHIC_BOARD:
    {
      port[0].data_w = graphic_board_write;
      port[0].data_r = graphic_board_read;
      break;
    }

    case SYSTEM_SMASH:
    {
      port[0].data_w = dummy_write;
      port[0].data_r = smash_1_read;
      break;
    }

    default:
    {
      port[0].data_w = dummy_write;
      port[0].data_r = dummy_read;
      break;
    }
  }

  switch (input.system[1])
  {
    case SYSTEM_GAMEPAD:
    {
      port[1].data_w = (input.dev[4] == DEVICE_PAD2B) ? dummy_write : gamepad_2_write;
      port[1].data_r = gamepad_2_read;
      break;
    }

    case SYSTEM_MOUSE:
    {
      port[1].data_w = mouse_write;
      port[1].data_r = mouse_read;
      break;
    }

    case SYSTEM_XE_1AP:
    {
      port[1].data_w = xe_1ap_2_write;
      port[1].data_r = xe_1ap_2_read;
      break;
    }

    case SYSTEM_ACTIVATOR:
    {
      port[1].data_w = activator_2_write;
      port[1].data_r = activator_2_read;
      break;
    }

    case SYSTEM_MENACER:
    {
      port[1].data_w = dummy_write;
      port[1].data_r = menacer_read;
      break;
    }

    case SYSTEM_JUSTIFIER:
    {
      port[1].data_w = justifier_write;
      port[1].data_r = justifier_read;
      break;
    }

    case SYSTEM_WAYPLAY:
    {
      port[1].data_w = wayplay_2_write;
      port[1].data_r = wayplay_2_read;
      break;
    }

    case SYSTEM_TEAMPLAYER:
    {
      port[1].data_w = teamplayer_2_write;
      port[1].data_r = teamplayer_2_read;
      break;
    }

    case SYSTEM_MASTERTAP:
    {
      port[1].data_w = mastertap_2_write;
      port[1].data_r = mastertap_2_read;
      break;
    }

    case SYSTEM_LIGHTPHASER:
    {
      port[1].data_w = dummy_write;
      port[1].data_r = phaser_2_read;
      break;
    }

    case SYSTEM_PADDLE:
    {
      port[1].data_w = paddle_2_write;
      port[1].data_r = paddle_2_read;
      break;
    }

    case SYSTEM_SPORTSPAD:
    {
      port[1].data_w = sportspad_2_write;
      port[1].data_r = sportspad_2_read;
      break;
    }

    case SYSTEM_GRAPHIC_BOARD:
    {
      port[1].data_w = graphic_board_write;
      port[1].data_r = graphic_board_read;
      break;
    }

    case SYSTEM_SMASH:
    {
      port[1].data_w = dummy_write;
      port[1].data_r = smash_2_read;
      break;
    }

    default:
    {
      port[1].data_w = dummy_write;
      port[1].data_r = dummy_read;
      break;
    }
  }

  /* External Port (unconnected) */
  port[2].data_w = dummy_write;
  port[2].data_r = dummy_read;
}


void io_reset(void)
{
  /* Reset I/O registers */
  if ((system_hw & SYSTEM_PBC) == SYSTEM_MD)
  {
    io_reg[0x00] = region_code | (config.bios & 1);
    io_reg[0x01] = 0x00;
    io_reg[0x02] = 0x00;
    io_reg[0x03] = 0x00;
    io_reg[0x04] = 0x00;
    io_reg[0x05] = 0x00;
    io_reg[0x06] = 0x00;
    io_reg[0x07] = 0xFF;
    io_reg[0x08] = 0x00;
    io_reg[0x09] = 0x00;
    io_reg[0x0A] = 0xFF;
    io_reg[0x0B] = 0x00;
    io_reg[0x0C] = 0x00;
    io_reg[0x0D] = 0xFB;
    io_reg[0x0E] = 0x00;
    io_reg[0x0F] = 0x00;

    /* CD unit detection */
    if (system_hw != SYSTEM_MCD)
    {
      io_reg[0x00] |= 0x20;
    }
  }
  else
  {
    /* Game Gear specific registers */
    io_reg[0x00] = 0x80 | (region_code >> 1);
    io_reg[0x01] = 0x00;
    io_reg[0x02] = 0xFF;
    io_reg[0x03] = 0x00;
    io_reg[0x04] = 0xFF;
    io_reg[0x05] = 0x00;
    io_reg[0x06] = 0xFF;

    /* initial !RESET input */
    io_reg[0x0D] = IO_RESET_HI;

    /* default !CONT input */
    if (system_hw != SYSTEM_PBC)
    {
      io_reg[0x0D] |= IO_CONT1_HI;
    }

    /* Memory Control register (Master System and Game Gear hardware only) */
    if ((system_hw & SYSTEM_SMS) || (system_hw & SYSTEM_GG))
    {
      /* RAM, I/O and either BIOS or Cartridge ROM are enabled */
      io_reg[0x0E] = (z80_readmap[0] == cart.rom + 0x400000) ? 0xE0 : 0xA8;
    }
    else
    {
      /* default value (no Memory Control register) */
      io_reg[0x0E] = 0x00;
    }

    /* I/O control register (Master System, Mega Drive and Game Gear hardware only) */
    if (system_hw >= SYSTEM_SMS)
    {
      /* on power-on, TR and TH are configured as inputs */
      io_reg[0x0F] = 0xFF;
    }
    else
    {
      /* on SG-1000 & Mark-III, TR is always an input and TH is not connected (always return 1) */
      io_reg[0x0F] = 0xF5;
    }
  }

  /* Reset connected peripherals */
  input_reset();
}


/*****************************************************************************
 * I/O ports access from 68k (Genesis mode)                                  *
 *                                                                           *
 *****************************************************************************/

void io_68k_write(unsigned int offset, unsigned int data)
{
  switch (offset)
  {
    case 0x01:  /* Port A Data */
    case 0x02:  /* Port B Data */
    case 0x03:  /* Port C Data */
    {
      /*
        D7 : Unused. This bit will return any value written to it
        D6 : TH pin output level (1=high, 0=low)
        D5 : TR pin output level (1=high, 0=low)
        D4 : TL pin output level (1=high, 0=low)
        D3 : D3 pin output level (1=high, 0=low)
        D2 : D2 pin output level (1=high, 0=low)
        D1 : D1 pin output level (1=high, 0=low)
        D0 : D0 pin output level (1=high, 0=low)
      */
      io_reg[offset] = data;
      port[offset-1].data_w(data, io_reg[offset + 3]);
      return;
    }

    case 0x04:  /* Port A Ctrl */
    case 0x05:  /* Port B Ctrl */
    case 0x06:  /* Port C Ctrl */
    {
      /*
        D7 : /HL output control (1=TH input level, 0=forced high)
        D6 : TH pin is 1=output, 0=input
        D5 : TR pin is 1=output, 0=input
        D4 : TL pin is 1=output, 0=input
        D3 : D3 pin is 1=output, 0=input
        D2 : D2 pin is 1=output, 0=input
        D1 : D1 pin is 1=output, 0=input
        D0 : D0 pin is 1=output, 0=input
      */
      if (data != io_reg[offset])
      {
        io_reg[offset] = data;
        port[offset-4].data_w(io_reg[offset-3], data);
      }
      return;
    }

    case 0x07:  /* Port A TxData */
    case 0x0A:  /* Port B TxData */
    case 0x0D:  /* Port C TxData */
    {
      io_reg[offset] = data;
      return;
    }

    case 0x09:  /* Port A S-Ctrl */
    case 0x0C:  /* Port B S-Ctrl */
    case 0x0F:  /* Port C S-Ctrl */
    {
      /*
        D7-D6 : Serial baud rate (00= 4800 bps, 01= 2400 bps, 10= 1200 bps, 11= 300 bps)
        D5 : TR pin functions as 1= serial input pin, 0= normal
        D4 : TL pin functions as 1= serial output pin, 0= normal
        D3 : 1= Make I/O chip strobe /HL low when a byte has been received, 0= Do nothing
        D2 : read-only (on read, 1= Error receiving current byte, 0= No error)
        D1 : read-only (on read, 1= Rxd buffer is ready to read, 0= Rxd buffer isn't ready)
        D0 : read-only (on read, 1= Txd buffer is full, 0= Can write to Txd buffer)
      */
      io_reg[offset] = data & 0xF8;
      return;
    }

    default:  /* Read-only ports */
    {
      return;
    }
  }
}

unsigned int io_68k_read(unsigned int offset)
{
  switch(offset)
  {
    case 0x01:  /* Port A Data */
    case 0x02:  /* Port B Data */
    case 0x03:  /* Port C Data */
    {
      /*
        D7 : Unused. This bit will return any value written to it
        D6 : TH pin input level (1=high, 0=low)
        D5 : TR pin input level (1=high, 0=low)
        D4 : TL pin input level (1=high, 0=low)
        D3 : D3 pin input level (1=high, 0=low)
        D2 : D2 pin input level (1=high, 0=low)
        D1 : D1 pin input level (1=high, 0=low)
        D0 : D0 pin input level (1=high, 0=low)
      */
      unsigned int mask = 0x80 | io_reg[offset + 3];
      unsigned int data = port[offset-1].data_r();
      return (io_reg[offset] & mask) | (data & ~mask);
    }

    default:  /* return register value */
    {
      return io_reg[offset];
    }
  }
}


/*****************************************************************************
 *  I/O ports access from Z80                                                *
 *                                                                           *
 *****************************************************************************/

void io_z80_write(unsigned int offset, unsigned int data, unsigned int cycles)
{
  /* I/O Control register */
  if (offset)
  {
    /* 
        Bit  Function
        --------------
        D7 : Port B TH pin output level (1=high, 0=low)
        D6 : Port B TR pin output level (1=high, 0=low)
        D5 : Port A TH pin output level (1=high, 0=low)
        D4 : Port A TR pin output level (1=high, 0=low)
        D3 : Port B TH pin direction (1=input, 0=output)
        D2 : Port B TR pin direction (1=input, 0=output)
        D1 : Port A TH pin direction (1=input, 0=output)
        D0 : Port A TR pin direction (1=input, 0=output)
    */

    /* Send TR/TH state to connected peripherals */
    port[0].data_w((data << 1) & 0x60, (~data << 5) & 0x60);
    port[1].data_w((data >> 1) & 0x60, (~data << 3) & 0x60);
    
    /* Check for TH low-to-high transitions on both ports */
    if ((!(io_reg[0x0F] & 0x80) && (data & 0x80)) ||
        (!(io_reg[0x0F] & 0x20) && (data & 0x20)))
    {
      /* Latch new HVC */
      hvc_latch = hctab[cycles % MCYCLES_PER_LINE] | 0x10000;
    }

    /* Japanese model specific */
    if (region_code == REGION_JAPAN_NTSC)
    {
      /* Reading TH & TR pins always return 0 when set as output */
      data &= 0x0F;
    }

    /* Update I/O Control register */
    io_reg[0x0F] = data;
  }
  else
  {
    /* Memory Control register */
    io_reg[0x0E] = data;

    /* Switch cartridge & BIOS ROM */
    sms_cart_switch(~data);
  }
}

unsigned int io_z80_read(unsigned int offset)
{
  /* Read port A & port B input data */
  unsigned int data = (port[0].data_r()) | (port[1].data_r() << 8);

  /* I/O control register value */
  unsigned int ctrl = io_reg[0x0F];

  /* I/O ports */
  if (offset)
  {
   /* 
     Bit  Function
     --------------
     D7 : Port B TH pin input
     D6 : Port A TH pin input
     D5 : CONT input (0 on Mega Drive hardware, 1 otherwise)
     D4 : RESET button (1: default, 0: pressed, only on Master System hardware)
     D3 : Port B TR pin input
     D2 : Port B TL pin input
     D1 : Port B Right pin input
     D0 : Port B Left pin input
    */
    data = ((data >> 10) & 0x0F) | (data & 0x40) | ((data >> 7) & 0x80) | io_reg[0x0D];

    /* clear !RESET input */
    io_reg[0x0D] |= IO_RESET_HI;

    /* Adjust port B TH state if configured as output */
    if (!(ctrl & 0x08))
    {
      data &= ~0x80;
      data |= (ctrl & 0x80);
    }

    /* Adjust port A TH state if configured as output */
    if (!(ctrl & 0x02))
    {
      data &= ~0x40;
      data |= ((ctrl & 0x20) << 1);
    }

    /* Adjust port B TR state if configured as output */
    if (!(ctrl & 0x04))
    {
      data &= ~0x08;
      data |= ((ctrl & 0x40) >> 3);
    }
  }
  else
  {
   /* 
     Bit  Function
     --------------
     D7 : Port B Down pin input
     D6 : Port B Up pin input
     D5 : Port A TR pin input
     D4 : Port A TL pin input
     D3 : Port A Right pin input
     D2 : Port A Left pin input
     D1 : Port A Down pin input
     D0 : Port A Up pin input
    */
    data = (data & 0x3F) | ((data >> 2) & 0xC0);

    /* Adjust port A TR state if configured as output */
    if (!(ctrl & 0x01))
    {
      data &= ~0x20;
      data |= ((ctrl & 0x10) << 1);
    }
  }

  return data;
}


/*****************************************************************************
 * Game Gear communication ports access                                       *
 *                                                                           *
 *****************************************************************************/

void io_gg_write(unsigned int offset, unsigned int data)
{
  switch (offset)
  {
    case 1: /* Parallel data register */
      io_reg[1] = data;
      return;

    case 2: /* Data direction register and NMI enable */
      io_reg[2] = data;
      return;

    case 3: /* Transmit data buffer */
      io_reg[3] = data;
      return;

    case 5: /* Serial control (bits 0-2 are read-only) */
      io_reg[5] = data & 0xF8;
      return;

    case 6: /* PSG Stereo output control */
      io_reg[6] = data;
      psg_config(Z80.cycles, config.psg_preamp, data);
      return;

    default: /* Read-only */
      return;
  }
}

unsigned int io_gg_read(unsigned int offset)
{
  switch (offset)
  {
    case 0: /* Mode Register */
      return (io_reg[0] & ~(input.pad[0] & INPUT_START));

    case 1: /* Parallel data register (not connected) */
      return ((io_reg[1] & ~(io_reg[2] & 0x7F)) | (io_reg[2] & 0x7F));

    case 2: /* Data direction register and NMI enable */
      return io_reg[2];

    case 3: /* Transmit data buffer */
      return io_reg[3];

    case 4: /* Receive data buffer */
      return io_reg[4];

    case 5: /* Serial control */
      return io_reg[5];

    default: /* Write-Only */
      return 0xFF;
  }
}

