/***************************************************************************************
 *  Genesis Plus
 *  Virtual System emulation
 *
 *  Support for "Genesis", "Genesis + CD" & "Master System" modes
 *
 *  Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003  Charles Mac Donald (original code)
 *  Copyright (C) 2007-2013  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 "eq.h"

/* Global variables */
t_bitmap bitmap;
t_snd snd;
uint32 mcycles_vdp;
uint8 system_hw;
uint8 system_bios;
uint32 system_clock;
const int16 SVP_cycles = 800; 

uint8 pause_b;
EQSTATE eq;
int16 llp,rrp;

/******************************************************************************************/
/* Audio subsystem                                                                        */
/******************************************************************************************/

int audio_init(int samplerate, double framerate)
{
  /* Number of M-cycles executed per second. */
  /* All emulated chips are kept in sync by using a common oscillator (MCLOCK)            */
  /*                                                                                      */
  /* The original console would run exactly 53693175 M-cycles per sec (53203424 for PAL), */
  /* 3420 M-cycles per line and 262 (313 for PAL) lines per frame, which gives an exact   */
  /* framerate of 59.92 (49.70 for PAL) frames per second.                                */
  /*                                                                                      */
  /* Since audio samples are generated at the end of the frame, to prevent audio skipping */
  /* or lag between emulated frames, number of samples rendered per frame must be set to  */
  /* output samplerate (number of samples played per second) divided by input framerate   */
  /* (number of frames emulated per seconds).                                             */
  /*                                                                                      */
  /* On some systems, we may want to achieve 100% smooth video rendering by synchronizing */
  /* frame emulation with VSYNC, which frequency is generally not exactly those values.   */
  /* In that case, input framerate (number of frames emulated per seconds) is the same as */
  /* output framerate (number of frames rendered per seconds) by the host video hardware. */
  /*                                                                                      */
  /* When no framerate is specified, base clock is set to original master clock value.    */
  /* Otherwise, it is set to number of M-cycles emulated per line (fixed) multiplied by   */
  /* number of lines per frame (VDP mode specific) multiplied by input framerate.         */
  /*                                                                                      */
  double mclk = framerate ? (MCYCLES_PER_LINE * (vdp_pal ? 313 : 262) * framerate) : system_clock;

  /* Shutdown first */
  audio_shutdown();

  /* Clear the sound data context */
  memset(&snd, 0, sizeof (snd));

  /* Initialize audio rates */
  snd.sample_rate = samplerate;
  snd.frame_rate  = framerate;

  /* Initialize Blip Buffers */
  snd.blips[0][0] = blip_new(samplerate / 10);
  snd.blips[0][1] = blip_new(samplerate / 10);
  if (!snd.blips[0][0] || !snd.blips[0][1])
  {
    audio_shutdown();
    return -1;
  }

  /* For maximal accuracy, sound chips are running at their original rate using common */
  /* master clock timebase so they remain perfectly synchronized together, while still */
  /* being synchronized with 68K and Z80 CPUs as well. Mixed sound chip output is then */
  /* resampled to desired rate at the end of each frame, using Blip Buffer.            */
  blip_set_rates(snd.blips[0][0], mclk, samplerate);
  blip_set_rates(snd.blips[0][1], mclk, samplerate);

  /* Initialize PSG core */
  SN76489_Init(snd.blips[0][0], snd.blips[0][1], (system_hw < SYSTEM_MARKIII) ? SN_DISCRETE : SN_INTEGRATED);

  /* Mega CD sound hardware */
  if (system_hw == SYSTEM_MCD)
  {
    /* allocate blip buffers */
    snd.blips[1][0] = blip_new(samplerate / 10);
    snd.blips[1][1] = blip_new(samplerate / 10);
    snd.blips[2][0] = blip_new(samplerate / 10);
    snd.blips[2][1] = blip_new(samplerate / 10);
    if (!snd.blips[1][0] || !snd.blips[1][1] || !snd.blips[2][0] || !snd.blips[2][1])
    {
      audio_shutdown();
      return -1;
    }

    /* Initialize PCM core */
    pcm_init(snd.blips[1][0], snd.blips[1][1]);

    /* Initialize CDD core */
    cdd_init(snd.blips[2][0], snd.blips[2][1]);
  }

  /* Set audio enable flag */
  snd.enabled = 1;

  /* Reset audio */
  audio_reset();

  return (0);
}

void audio_reset(void)
{
  int i,j;
  
  /* Clear blip buffers */
  for (i=0; i<3; i++)
  {
    for (j=0; j<2; j++)
    {
      if (snd.blips[i][j])
      {
        blip_clear(snd.blips[i][j]);
      }
    }
  }

  /* Low-Pass filter */
  llp = 0;
  rrp = 0;

  /* 3 band EQ */
  audio_set_equalizer();
}

void audio_set_equalizer(void)
{
  init_3band_state(&eq,config.low_freq,config.high_freq,snd.sample_rate);
  eq.lg = (double)(config.lg) / 100.0;
  eq.mg = (double)(config.mg) / 100.0;
  eq.hg = (double)(config.hg) / 100.0;
}

void audio_shutdown(void)
{
  int i,j;
  
  /* Delete blip buffers */
  for (i=0; i<3; i++)
  {
    for (j=0; j<2; j++)
    {
      blip_delete(snd.blips[i][j]);
      snd.blips[i][j] = 0;
    }
  }
}

int audio_update(int16 *buffer)
{
  /* run sound chips until end of frame */
  int size = sound_update(mcycles_vdp);

  /* Mega CD specific */
  if (system_hw == SYSTEM_MCD)
  {
    /* sync PCM chip with other sound chips */
    pcm_update(size);

    /* read CDDA samples */
    cdd_read_audio(size);
  }

#ifdef ALIGN_SND
  /* return an aligned number of samples if required */
  size &= ALIGN_SND;
#endif

  /* resample FM & PSG mixed stream to output buffer */
#ifdef LSB_FIRST
  blip_read_samples(snd.blips[0][0], buffer, size);
  blip_read_samples(snd.blips[0][1], buffer + 1, size);
#else
  blip_read_samples(snd.blips[0][0], buffer + 1, size);
  blip_read_samples(snd.blips[0][1], buffer, size);
#endif

  /* Mega CD specific */
  if (system_hw == SYSTEM_MCD)
  {
    /* resample PCM & CD-DA streams to output buffer */
#ifdef LSB_FIRST
    blip_mix_samples(snd.blips[1][0], buffer, size);
    blip_mix_samples(snd.blips[1][1], buffer + 1, size);
    blip_mix_samples(snd.blips[2][0], buffer, size);
    blip_mix_samples(snd.blips[2][1], buffer + 1, size);
#else
    blip_mix_samples(snd.blips[1][0], buffer + 1, size);
    blip_mix_samples(snd.blips[1][1], buffer, size);
    blip_mix_samples(snd.blips[2][0], buffer + 1, size);
    blip_mix_samples(snd.blips[2][1], buffer, size);
#endif
  }

  /* Audio filtering */
  if (config.filter)
  {
    int samples = size;
    int16 *out = buffer;
    int32 l, r;

    if (config.filter & 1)
    {
      /* single-pole low-pass filter (6 dB/octave) */
      uint32 factora  = config.lp_range;
      uint32 factorb  = 0x10000 - factora;

      /* restore previous sample */
      l = llp;
      r = rrp;

      do
      {
        /* apply low-pass filter */
        l = l*factora + out[0]*factorb;
        r = r*factora + out[1]*factorb;

        /* 16.16 fixed point */
        l >>= 16;
        r >>= 16;

        /* update sound buffer */
        *out++ = l;
        *out++ = r;
      }
      while (--samples);

      /* save last samples for next frame */
      llp = l;
      rrp = r;
    }
    else if (config.filter & 2)
    {
      do
      {
        /* 3 Band EQ */
        l = do_3band(&eq,out[0]);
        r = do_3band(&eq,out[1]);

        /* clipping (16-bit samples) */
        if (l > 32767) l = 32767;
        else if (l < -32768) l = -32768;
        if (r > 32767) r = 32767;
        else if (r < -32768) r = -32768;

        /* update sound buffer */
        *out++ = l;
        *out++ = r;
      }
      while (--samples);
    }
  }

  /* Mono output mixing */
  if (config.mono)
  {
    int16 out;
    int samples = size;
    do
    {
      out = (buffer[0] + buffer[1]) / 2;
      *buffer++ = out;
      *buffer++ = out;
    }
    while (--samples);
  }

#ifdef LOGSOUND
  error("%d samples returned\n\n",size);
#endif

  return size;
}

/****************************************************************
 * Virtual System emulation
 ****************************************************************/
void system_init(void)
{
  gen_init();
  io_init();
  vdp_init();
  render_init();
  sound_init();
}

void system_reset(void)
{
  gen_reset(1);
  io_reset();
  render_reset();
  vdp_reset();
  sound_reset();
  audio_reset();
}

void system_frame_gen(int do_skip)
{
  /* line counters */
  int start, end, line = 0;

  /* Z80 interrupt flag */
  int zirq = 1;

  /* reload H Counter */
  int h_counter = reg[10];

  /* reset frame cycle counter */
  mcycles_vdp = 0;

  /* reload V Counter */
  v_counter = lines_per_frame - 1;

  /* reset VDP FIFO */
  fifo_write_cnt = 0;
  fifo_slots = 0;

  /* update 6-Buttons & Lightguns */
  input_refresh();

  /* display changed during VBLANK */
  if (bitmap.viewport.changed & 2)
  {
    /* interlaced modes */
    int old_interlaced = interlaced;
    interlaced = (reg[12] & 0x02) >> 1;

    if (old_interlaced != interlaced)
    {
      /* double resolution mode */
      im2_flag = ((reg[12] & 0x06) == 0x06);

      /* reset field status flag */
      odd_frame = 1;

      /* video mode has changed */
      bitmap.viewport.changed = 5;

      /* update rendering mode */
      if (reg[1] & 0x04)
      {
        if (im2_flag)
        {
          render_bg = (reg[11] & 0x04) ? render_bg_m5_im2_vs : render_bg_m5_im2;
          render_obj = (reg[12] & 0x08) ? render_obj_m5_im2_ste : render_obj_m5_im2;
        }
        else
        {
          render_bg = (reg[11] & 0x04) ? render_bg_m5_vs : render_bg_m5;
          render_obj = (reg[12] & 0x08) ? render_obj_m5_ste : render_obj_m5;
        }
      }
    }
    else
    {
      /* clear flag */
      bitmap.viewport.changed &= ~2;
    }

    /* active screen height */
    if (reg[1] & 0x04)
    {
      /* Mode 5 */
      if (reg[1] & 0x08)
      {
        /* 240 active lines */
        bitmap.viewport.h = 240;
        bitmap.viewport.y = (config.overscan & 1) * 24 * vdp_pal;
      }
      else
      {
        /* 224 active lines */
        bitmap.viewport.h = 224;
        bitmap.viewport.y = (config.overscan & 1) * (8 + (24 * vdp_pal));
      }
    }
    else
    {
      /* Mode 4 (192 active lines) */
      bitmap.viewport.h = 192;
      bitmap.viewport.y = (config.overscan & 1) * 24 * (vdp_pal + 1);
    }

    /* active screen width */
    bitmap.viewport.w = 256 + ((reg[12] & 0x01) << 6);
  }

  /* clear VBLANK, DMA, FIFO FULL & field flags */
  status &= 0xFEE5;

  /* set FIFO EMPTY flag */
  status |= 0x0200;

  /* even/odd field flag (interlaced modes only) */
  odd_frame ^= 1;
  if (interlaced)
  {
    status |= (odd_frame << 4);
  }

  /* update VDP DMA */
  if (dma_length)
  {
    vdp_dma_update(0);
  }

  /* render last line of overscan */
  if (bitmap.viewport.y > 0)
  {
    blank_line(v_counter, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
  }

  /* parse first line of sprites */
  if (reg[1] & 0x40)
  {
    parse_satb(-1);
  }

  /* run 68k & Z80 */
  m68k_run(MCYCLES_PER_LINE);
  if (zstate == 1)
  {
    z80_run(MCYCLES_PER_LINE);
  }
  else
  {
    Z80.cycles = MCYCLES_PER_LINE;
  }

  /* run SVP chip */
  if (svp)
  {
    ssp1601_run(SVP_cycles);
  }

  /* update line cycle count */
  mcycles_vdp += MCYCLES_PER_LINE;

  /* Active Display */
  do
  {
    /* update V Counter */
    v_counter = line;

    /* update 6-Buttons & Lightguns */
    input_refresh();

    /* H Interrupt */
    if(--h_counter < 0)
    {
      /* reload H Counter */
      h_counter = reg[10];
      
      /* interrupt level 4 */
      hint_pending = 0x10;
      if (reg[0] & 0x10)
      {
        m68k_update_irq(4);
      }
    }

    /* update VDP DMA */
    if (dma_length)
    {
      vdp_dma_update(mcycles_vdp);
    }

    /* render scanline */
    if (!do_skip)
    {
      render_line(line);
    }

    /* run 68k & Z80 */
    m68k_run(mcycles_vdp + MCYCLES_PER_LINE);
    if (zstate == 1)
    {
      z80_run(mcycles_vdp + MCYCLES_PER_LINE);
    }
    else
    {
      Z80.cycles = mcycles_vdp + MCYCLES_PER_LINE;
    }

    /* run SVP chip */
    if (svp)
    {
      ssp1601_run(SVP_cycles);
    }

    /* update line cycle count */
    mcycles_vdp += MCYCLES_PER_LINE;
  }
  while (++line < bitmap.viewport.h);

  /* end of active display */
  v_counter = line;

  /* set VBLANK flag */
  status |= 0x08;

  /* overscan area */
  start = lines_per_frame - bitmap.viewport.y;
  end   = bitmap.viewport.h + bitmap.viewport.y;

  /* check viewport changes */
  if ((bitmap.viewport.w != bitmap.viewport.ow) || (bitmap.viewport.h != bitmap.viewport.oh))
  {
    bitmap.viewport.ow = bitmap.viewport.w;
    bitmap.viewport.oh = bitmap.viewport.h;
    bitmap.viewport.changed |= 1;
  }

  /* update 6-Buttons & Lightguns */
  input_refresh();

  /* H Interrupt */
  if(--h_counter < 0)
  {
    /* reload H Counter */
    h_counter = reg[10];

    /* interrupt level 4 */
    hint_pending = 0x10;
    if (reg[0] & 0x10)
    {
      m68k_update_irq(4);
    }
  }

  /* update VDP DMA */
  if (dma_length)
  {
    vdp_dma_update(mcycles_vdp);
  }

  /* render overscan */
  if (line < end)
  {
    blank_line(line, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
  }

  /* update inputs before VINT (Warriors of Eternal Sun) */
  osd_input_update();

  /* delay between VINT flag & V Interrupt (Ex-Mutants, Tyrant) */
  m68k_run(mcycles_vdp + 588);
  status |= 0x80;

  /* delay between VBLANK flag & V Interrupt (Dracula, OutRunners, VR Troopers) */
  m68k_run(mcycles_vdp + 788);
  if (zstate == 1)
  {
    z80_run(mcycles_vdp + 788);
  }
  else
  {
    Z80.cycles = mcycles_vdp + 788;
  }

  /* V Interrupt */
  vint_pending = 0x20;
  if (reg[1] & 0x20)
  {
    m68k_set_irq(6);
  }

  /* assert Z80 interrupt */
  Z80.irq_state = ASSERT_LINE;

  /* run 68k & Z80 until end of line */
  m68k_run(mcycles_vdp + MCYCLES_PER_LINE);
  if (zstate == 1)
  {
    z80_run(mcycles_vdp + MCYCLES_PER_LINE);
  }
  else
  {
    Z80.cycles = mcycles_vdp + MCYCLES_PER_LINE;
  }

  /* run SVP chip */
  if (svp)
  {
    ssp1601_run(SVP_cycles);
  }

  /* update line cycle count */
  mcycles_vdp += MCYCLES_PER_LINE;

  /* increment line count */
  line++;

  /* Vertical Blanking */
  do
  {
    /* update V Counter */
    v_counter = line;

    /* update 6-Buttons & Lightguns */
    input_refresh();

    /* render overscan */
    if ((line < end) || (line >= start))
    {
      blank_line(line, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
    }

    if (zirq)
    {
      /* Z80 interrupt is asserted exactly for one line */
      m68k_run(mcycles_vdp + 788);
      if (zstate == 1)
      {
        z80_run(mcycles_vdp + 788);
      }
      else
      {
        Z80.cycles = mcycles_vdp + 788;
      }

      /* clear Z80 interrupt */
      Z80.irq_state = CLEAR_LINE;
      zirq = 0;
    }

    /* run 68k & Z80 */
    m68k_run(mcycles_vdp + MCYCLES_PER_LINE);
    if (zstate == 1)
    {
      z80_run(mcycles_vdp + MCYCLES_PER_LINE);
    }
    else
    {
      Z80.cycles = mcycles_vdp + MCYCLES_PER_LINE;
    }

    /* run SVP chip */
    if (svp)
    {
      ssp1601_run(SVP_cycles);
    }

    /* update line cycle count */
    mcycles_vdp += MCYCLES_PER_LINE;
  }
  while (++line < (lines_per_frame - 1));

  /* adjust CPU cycle counters for next frame */
  m68k.cycles -= mcycles_vdp;
  Z80.cycles -= mcycles_vdp;
}

void system_frame_scd(int do_skip)
{
  /* line counters */
  int start, end, line = 0;

  /* Z80 interrupt flag */
  int zirq = 1;

  /* reload H Counter */
  int h_counter = reg[10];

  /* reset frame cycle counters */
  mcycles_vdp = 0;
  scd.cycles = 0;
  
  /* reload V Counter */
  v_counter = lines_per_frame - 1;

  /* reset VDP FIFO */
  fifo_write_cnt = 0;
  fifo_slots = 0;

  /* update 6-Buttons & Lightguns */
  input_refresh();

  /* display changed during VBLANK */
  if (bitmap.viewport.changed & 2)
  {
    /* interlaced modes */
    int old_interlaced = interlaced;
    interlaced = (reg[12] & 0x02) >> 1;

    if (old_interlaced != interlaced)
    {
      /* double resolution mode */
      im2_flag = ((reg[12] & 0x06) == 0x06);

      /* reset field status flag */
      odd_frame = 1;

      /* video mode has changed */
      bitmap.viewport.changed = 5;

      /* update rendering mode */
      if (reg[1] & 0x04)
      {
        if (im2_flag)
        {
          render_bg = (reg[11] & 0x04) ? render_bg_m5_im2_vs : render_bg_m5_im2;
          render_obj = (reg[12] & 0x08) ? render_obj_m5_im2_ste : render_obj_m5_im2;
        }
        else
        {
          render_bg = (reg[11] & 0x04) ? render_bg_m5_vs : render_bg_m5;
          render_obj = (reg[12] & 0x08) ? render_obj_m5_ste : render_obj_m5;
        }
      }
    }
    else
    {
      /* clear flag */
      bitmap.viewport.changed &= ~2;
    }

    /* active screen height */
    if (reg[1] & 0x04)
    {
      bitmap.viewport.h = 224 + ((reg[1] & 0x08) << 1);
      bitmap.viewport.y = (config.overscan & 1) * ((240 + 48*vdp_pal - bitmap.viewport.h) >> 1);
    }
    else
    {
      bitmap.viewport.h = 192;
      bitmap.viewport.y = (config.overscan & 1) * 24 * (vdp_pal + 1);
    }

    /* active screen width */
    bitmap.viewport.w = 256 + ((reg[12] & 0x01) << 6);
  }

  /* clear VBLANK, DMA, FIFO FULL & field flags */
  status &= 0xFEE5;

  /* set FIFO EMPTY flag */
  status |= 0x0200;

  /* even/odd field flag (interlaced modes only) */
  odd_frame ^= 1;
  if (interlaced)
  {
    status |= (odd_frame << 4);
  }

  /* update VDP DMA */
  if (dma_length)
  {
    vdp_dma_update(0);
  }

  /* render last line of overscan */
  if (bitmap.viewport.y > 0)
  {
    blank_line(v_counter, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
  }

  /* parse first line of sprites */
  if (reg[1] & 0x40)
  {
    parse_satb(-1);
  }

  /* run both 68k & CD hardware */
  scd_update(MCYCLES_PER_LINE);

  /* run Z80 */
  if (zstate == 1)
  {
    z80_run(MCYCLES_PER_LINE);
  }
  else
  {
    Z80.cycles = MCYCLES_PER_LINE;
  }

  /* update line cycle count */
  mcycles_vdp += MCYCLES_PER_LINE;

  /* Active Display */
  do
  {
    /* update V Counter */
    v_counter = line;

    /* update 6-Buttons & Lightguns */
    input_refresh();

    /* H Interrupt */
    if(--h_counter < 0)
    {
      /* reload H Counter */
      h_counter = reg[10];
      
      /* interrupt level 4 */
      hint_pending = 0x10;
      if (reg[0] & 0x10)
      {
        m68k_update_irq(4);
      }
    }

    /* update VDP DMA */
    if (dma_length)
    {
      vdp_dma_update(mcycles_vdp);
    }

    /* render scanline */
    if (!do_skip)
    {
      render_line(line);
    }

    /* run both 68k & CD hardware */
    scd_update(mcycles_vdp + MCYCLES_PER_LINE);

    /* run Z80 */
    if (zstate == 1)
    {
      z80_run(mcycles_vdp + MCYCLES_PER_LINE);
    }
    else
    {
      Z80.cycles = mcycles_vdp + MCYCLES_PER_LINE;
    }

    /* update line cycle count */
    mcycles_vdp += MCYCLES_PER_LINE;
  }
  while (++line < bitmap.viewport.h);

  /* end of active display */
  v_counter = line;

  /* set VBLANK flag */
  status |= 0x08;

  /* overscan area */
  start = lines_per_frame - bitmap.viewport.y;
  end   = bitmap.viewport.h + bitmap.viewport.y;

  /* check viewport changes */
  if ((bitmap.viewport.w != bitmap.viewport.ow) || (bitmap.viewport.h != bitmap.viewport.oh))
  {
    bitmap.viewport.ow = bitmap.viewport.w;
    bitmap.viewport.oh = bitmap.viewport.h;
    bitmap.viewport.changed |= 1;
  }

  /* update 6-Buttons & Lightguns */
  input_refresh();

  /* H Interrupt */
  if(--h_counter < 0)
  {
    /* reload H Counter */
    h_counter = reg[10];

    /* interrupt level 4 */
    hint_pending = 0x10;
    if (reg[0] & 0x10)
    {
      m68k_update_irq(4);
    }
  }

  /* update VDP DMA */
  if (dma_length)
  {
    vdp_dma_update(mcycles_vdp);
  }

  /* render overscan */
  if (line < end)
  {
    blank_line(line, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
  }

  /* update inputs before VINT (Warriors of Eternal Sun) */
  osd_input_update();

  /* delay between VINT flag & V Interrupt (Ex-Mutants, Tyrant) */
  m68k_run(mcycles_vdp + 588);
  status |= 0x80;

  /* delay between VBLANK flag & V Interrupt (Dracula, OutRunners, VR Troopers) */
  m68k_run(mcycles_vdp + 788);
  if (zstate == 1)
  {
    z80_run(mcycles_vdp + 788);
  }
  else
  {
    Z80.cycles = mcycles_vdp + 788;
  }

  /* V Interrupt */
  vint_pending = 0x20;
  if (reg[1] & 0x20)
  {
    m68k_set_irq(6);
  }

  /* assert Z80 interrupt */
  Z80.irq_state = ASSERT_LINE;

  /* run both 68k & CD hardware */
  scd_update(mcycles_vdp + MCYCLES_PER_LINE);

  /* run Z80 until end of line */
  if (zstate == 1)
  {
    z80_run(mcycles_vdp + MCYCLES_PER_LINE);
  }
  else
  {
    Z80.cycles = mcycles_vdp + MCYCLES_PER_LINE;
  }

  /* update line cycle count */
  mcycles_vdp += MCYCLES_PER_LINE;

  /* increment line count */
  line++;

  /* Vertical Blanking */
  do
  {
    /* update V Counter */
    v_counter = line;

    /* update 6-Buttons & Lightguns */
    input_refresh();

    /* render overscan */
    if ((line < end) || (line >= start))
    {
      blank_line(line, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
    }

    if (zirq)
    {
      /* Z80 interrupt is asserted exactly for one line */
      m68k_run(mcycles_vdp + 788);
      if (zstate == 1)
      {
        z80_run(mcycles_vdp + 788);
      }
      else
      {
        Z80.cycles = mcycles_vdp + 788;
      }

      /* clear Z80 interrupt */
      Z80.irq_state = CLEAR_LINE;
      zirq = 0;
    }

    /* run both 68k & CD hardware */
    scd_update(mcycles_vdp + MCYCLES_PER_LINE);

    /* run Z80 */
    if (zstate == 1)
    {
      z80_run(mcycles_vdp + MCYCLES_PER_LINE);
    }
    else
    {
      Z80.cycles = mcycles_vdp + MCYCLES_PER_LINE;
    }

    /* update line cycle count */
    mcycles_vdp += MCYCLES_PER_LINE;
  }
  while (++line < (lines_per_frame - 1));
  
  /* prepare for next SCD frame */
  scd_end_frame(scd.cycles);

  /* adjust CPU cycle counters for next frame */
  Z80.cycles  -= mcycles_vdp;
  m68k.cycles -= mcycles_vdp;
}

void system_frame_sms(int do_skip)
{
  /* line counter */
  int start, end, line = 0;

  /* reload H Counter */
  int h_counter = reg[10];

  /* reset line master cycle count */
  mcycles_vdp = 0;

  /* reload V Counter */
  v_counter = lines_per_frame - 1;

  /* reset VDP FIFO */
  fifo_write_cnt = 0;
  fifo_slots = 0;

  /* update 6-Buttons & Lightguns */
  input_refresh();

  /* display changed during VBLANK */
  if (bitmap.viewport.changed & 2)
  {
    bitmap.viewport.changed &= ~2;

    if (system_hw & SYSTEM_MD)
    {
      /* interlaced mode */
      int old_interlaced  = interlaced;
      interlaced = (reg[12] & 0x02) >> 1;
      if (old_interlaced != interlaced)
      {
        im2_flag = ((reg[12] & 0x06) == 0x06);
        odd_frame = 1;
        bitmap.viewport.changed = 5;

        /* update rendering mode */
        if (reg[1] & 0x04)
        {
          if (im2_flag)
          {
            render_bg = (reg[11] & 0x04) ? render_bg_m5_im2_vs : render_bg_m5_im2;
            render_obj = render_obj_m5_im2;

          }
          else
          {
            render_bg = (reg[11] & 0x04) ? render_bg_m5_vs : render_bg_m5;
            render_obj = render_obj_m5;
          }
        }
      }

      /* active screen height */
      if (reg[1] & 0x04)
      {
        bitmap.viewport.h = 224 + ((reg[1] & 0x08) << 1);
        bitmap.viewport.y = (config.overscan & 1) * ((240 + 48*vdp_pal - bitmap.viewport.h) >> 1);
      }
      else
      {
        bitmap.viewport.h = 192;
        bitmap.viewport.y = (config.overscan & 1) * 24 * (vdp_pal + 1);
      }

      /* active screen width */
      bitmap.viewport.w = 256 + ((reg[12] & 0x01) << 6);
    }
    else
    {
      /* check for VDP extended modes */
      int mode = (reg[0] & 0x06) | (reg[1] & 0x18);

      /* update active height */
      if (mode == 0x0E)
      {
        bitmap.viewport.h = 240;
      }
      else if (mode == 0x16)
      {
        bitmap.viewport.h = 224;
      }
      else
      {
        bitmap.viewport.h = 192;
      }

      /* update vertical overscan */
      if (config.overscan & 1)
      {
        bitmap.viewport.y = (240 + 48*vdp_pal - bitmap.viewport.h) >> 1;
      }
      else
      {
        if ((system_hw == SYSTEM_GG) && !config.gg_extra)
        {
          /* Display area reduced to 160x144 */
          bitmap.viewport.y = (144 - bitmap.viewport.h) / 2;
        }
        else
        {
          bitmap.viewport.y = 0;
        }
      }
    }
  }

  /* Detect pause button input (in Game Gear Mode, NMI is not generated) */
  if (system_hw != SYSTEM_GG)
  {
    if (input.pad[0] & INPUT_START)
    {
      /* NMI is edge-triggered */
      if (!pause_b)
      {
        pause_b = 1;
        z80_set_nmi_line(ASSERT_LINE);
        z80_set_nmi_line(CLEAR_LINE);
      }
    }
    else
    {
      pause_b = 0;
    }
  }

  /* 3-D glasses faking: skip rendering of left lens frame */
  do_skip |= (work_ram[0x1ffb] & cart.special & HW_3D_GLASSES);

  /* Mega Drive VDP specific */
  if (system_hw & SYSTEM_MD)
  {
    /* clear VBLANK, DMA & field flags */
    status &= 0xE5;

    /* even/odd field flag (interlaced modes only) */
    odd_frame ^= 1;
    if (interlaced)
    {
      status |= (odd_frame << 4);
    }

    /* update VDP DMA */
    if (dma_length)
    {
      vdp_dma_update(0);
    }
  }

  /* Master System & Game Gear VDP specific */
  if (system_hw < SYSTEM_MD)
  {
    /* Sprites are still processed during vertical borders */
    if (reg[1] & 0x40)
    {
      render_obj(1);
    }
  }

  /* render last line of overscan */
  if (bitmap.viewport.y > 0)
  {
    blank_line(v_counter, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
  }

  /* parse first line of sprites (on Master System VDP, pre-processing still occurs when display is disabled) */
  if ((reg[1] & 0x40) || (system_hw < SYSTEM_MD))
  {
    parse_satb(-1);
  }

  /* run Z80 */
  z80_run(MCYCLES_PER_LINE);

  /* update line cycle count */
  mcycles_vdp += MCYCLES_PER_LINE;

  /* latch Vertical Scroll register */
  vscroll = reg[0x09];

  /* Active Display */
  do
  {
    /* update VDP DMA (Mega Drive VDP specific) */
    if (dma_length)
    {
      vdp_dma_update(mcycles_vdp);
    }

    /* make sure we didn't already render that line */
    if (v_counter != line)
    {
      /* update V Counter */
      v_counter = line;

      /* render scanline */
      if (!do_skip)
      {
        render_line(line);
      }
    }

    /* update 6-Buttons & Lightguns */
    input_refresh();

    /* H Interrupt */
    if(--h_counter < 0)
    {
      /* reload H Counter */
      h_counter = reg[10];
      
      /* interrupt level 4 */
      hint_pending = 0x10;
      if (reg[0] & 0x10)
      {
        /* cycle-accurate HINT */
        /* IRQ line is latched between instructions, during instruction last cycle.       */
        /* This means that if Z80 cycle count is exactly a multiple of MCYCLES_PER_LINE,  */
        /* interrupt should be triggered AFTER the next instruction.                      */
        if ((Z80.cycles % MCYCLES_PER_LINE) == 0)
        {
          z80_run(Z80.cycles + 1);
        }

        Z80.irq_state = ASSERT_LINE;
      }
    }

    /* run Z80 */
    z80_run(mcycles_vdp + MCYCLES_PER_LINE);

    /* update line cycle count */
    mcycles_vdp += MCYCLES_PER_LINE;
  }
  while (++line < bitmap.viewport.h);

  /* end of active display */
  v_counter = line;

  /* Mega Drive VDP specific */
  if (system_hw & SYSTEM_MD)
  {
    /* set VBLANK flag */
    status |= 0x08;
  }

  /* overscan area */
  start = lines_per_frame - bitmap.viewport.y;
  end   = bitmap.viewport.h + bitmap.viewport.y;

  /* check viewport changes */
  if ((bitmap.viewport.w != bitmap.viewport.ow) || (bitmap.viewport.h != bitmap.viewport.oh))
  {
    bitmap.viewport.ow = bitmap.viewport.w;
    bitmap.viewport.oh = bitmap.viewport.h;
    bitmap.viewport.changed |= 1;
  }

  /* update 6-Buttons & Lightguns */
  input_refresh();

  /* H Interrupt */
  if(--h_counter < 0)
  {
    /* reload H Counter */
    h_counter = reg[10];

    /* interrupt level 4 */
    hint_pending = 0x10;
    if (reg[0] & 0x10)
    {
      /* cycle-accurate HINT */
      if ((Z80.cycles % MCYCLES_PER_LINE) == 0)
      {
        z80_run(Z80.cycles + 1);
      }

      Z80.irq_state = ASSERT_LINE;
    }
  }

  /* update VDP DMA (Mega Drive VDP specific) */
  if (dma_length)
  {
    vdp_dma_update(mcycles_vdp);
  }

  /* render overscan */
  if (line < end)
  {
    blank_line(line, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
  }

  /* update inputs before VINT */
  osd_input_update();

  /* run Z80 until end of line */
  z80_run(mcycles_vdp + MCYCLES_PER_LINE);

  /* make sure VINT flag was not cleared by last instruction */
  if (v_counter == line)
  {
    /* Set VINT flag */
    status |= 0x80;

    /* V Interrupt */
    vint_pending = 0x20;
    if (reg[1] & 0x20)
    {
      Z80.irq_state = ASSERT_LINE;
    }
  }

  /* update line cycle count */
  mcycles_vdp += MCYCLES_PER_LINE;

  /* increment line count */
  line++;

  /* Vertical Blanking */
  do
  {
    /* update V Counter */
    v_counter = line;

    /* update 6-Buttons & Lightguns */
    input_refresh();

    /* Master System & Game Gear VDP specific */
    if ((system_hw < SYSTEM_MD) && (line > (lines_per_frame - 16)))
    {
      /* Sprites are still processed during top border */
      render_obj((line - lines_per_frame) & 1);
      parse_satb(line - lines_per_frame);
    }

    /* render overscan */
    if ((line < end) || (line >= start))
    {
      blank_line(line, -bitmap.viewport.x, bitmap.viewport.w + 2*bitmap.viewport.x);
    }

    /* run Z80 */
    z80_run(mcycles_vdp + MCYCLES_PER_LINE);

    /* update line cycle count */
    mcycles_vdp += MCYCLES_PER_LINE;
  }
  while (++line < (lines_per_frame - 1));

  /* adjust Z80 cycle count for next frame */
  Z80.cycles -= mcycles_vdp;
}
