/*
*    DivaPC ARM C source
*
*    VID.C.SCROLL  - BIOS scrolling functions
*
*
*    05-07-93 INH  Original
*                  CGA char height is always 8, attrib is actual value
*    14-04-94      WinDrive2: Make sure no scrolling in Windows mode
*/

#include <string.h>

#include "sys.h.stdtypes"
#include "sys.h.sys"
#include "sys.h.FEState"
#include "vid.h.modes"
#include "vid.h.vids"
#include "vid.h.ports"
#include "vid.h.scroll"
#include "vid.h.cursor"
#include "vid.h.palette"

#define LSB(r)  ((r) & 0xFF)
#define MSB(r)  ((r) >> 8)

/* Attribute expand functions --------------------------- */

/* These convert the fill value as supplied to the BIOS into
   a data value for use in the framebuffer or on the screen
*/

static int FBAttrib ( int a )
{
  switch  (VID_ModeType)
  {
    case MODE_VGA:
      return (a & 0xF) * 0x11;

    case MODE_256CLR:
      return a;

    case MODE_TEXT:
      return (a<<8) + 0x20;

    case MODE_CGA45:
      return (a&3) * 0x55;

    case MODE_CGA6:
      return (a&1) * 0xFF;
  }
  return a;
}

/* -------------- */

static int ScrnAttrib ( int a )    /* Not used in text or CGA */
{
  switch ( VID_ModeType )
  {
    case MODE_TEXT:
      return ((a >> 4) & 0xF) * 0x11;

    case MODE_CGA45:
      return (a & 3) * 0x11;

    case MODE_CGA6:
      return (a & 1) * 0x11;

    case MODE_VGA:
      return (a & 0xF) * 0x11;

    case MODE_256CLR:
      return VID_256colourXlate[a & 0xFF];
  }
  return 0;
}

/* --------------- */

static bool SeparateScreenImage(void)
{
  return ( VID_FullScreenActive ||
       (VID_ModeType & (MODE_TEXT|MODE_CGA45|MODE_CGA6)) != 0 );
}


/* ==============================================================

   The Text & CGA MemoryMove routines do the PC memory portion of
   the scroll. These two are both considerably different to the
   standard VGA scroll routines.

   If src_line > dst_line, we are scrolling upwards, and
     src_line and dst_line should increment. If not, we are
     scrolling downwards, src_line and dst_line should decrement.
*/

static void TextMemoryMove ( BYTE *baseptr, int left, int width,
       int src_line, int dst_line, int nlines )
{
  register BYTE *pDst, *pSrc;
  register int i, lineinc;

  /* Each character takes 8 framebuffer bytes */
  pDst = baseptr + (dst_line*VID_TextModeCols+left)*8;
  pSrc = baseptr + (src_line*VID_TextModeCols+left)*8;
  if ( src_line > dst_line )
    lineinc = VID_TextModeCols*8;
  else
    lineinc = -VID_TextModeCols*8;
  width = width*8;

  while ( nlines-- > 0 )
  {
    for (i=width-8; i>=0; i-=8)
    {
      pDst[i] = pSrc[i];     /* Copy char */
      pDst[i+1] = pSrc[i+1]; /* Copy attribute */
    }
    pDst+=lineinc;
    pSrc+=lineinc;
  }
}

/* --------------------------- */


static void CGAMemoryMove ( BYTE *baseptr, int left, int width,
       int src_line, int dst_line, int nlines )
{
  register BYTE *pDst, *pSrc;
  register int i, lineinc;

  /* Scroll plane 0 data - each 'PC' byte is worth 4 in the frame
     buffer. We can assume each line is 80 PC bytes wide, and
     the character height is 8. Allowing for the 2-to-1 interleave
     factor, each character row takes 80*4 = 320 PC bytes */

  pDst = baseptr + (dst_line*320+left)*4;
  pSrc = baseptr + (src_line*320+left)*4;
  if ( src_line > dst_line )
    lineinc = 80*4;
  else
    lineinc = -80*4;
  width = width*4;   /* Width in frame buffer terms */
  nlines = nlines*4; /* Four line-pairs per row */

  while ( nlines-- > 0 )
  {
    for (i=width-4; i>=0; i-=4)
    {
      pDst[i] = pSrc[i];
      pDst[i+0x8000] = pSrc[i+0x8000];
    }
    pDst+=lineinc;
    pSrc+=lineinc;
  }
}

/* --------------------------- */

/* GraphMemoryMove is used for both VGA memory scrolling and
   screen image scrolling.
*/

static void GraphMemoryMove ( BYTE *baseptr, int left, int width,
       int src_line, int dst_line, int nlines, int charheight )
{
  register BYTE *pDst, *pSrc;
  register int lineinc;

  if ( src_line > dst_line )  /* Copy up */
  {
    src_line = src_line*charheight;
    dst_line = dst_line*charheight;
    lineinc = VIDS_ArmScreenWidthBytes;
  }
  else /* Copy down - start at very bottom line */
  {
    src_line = (src_line+1)*charheight-1;
    dst_line = (dst_line+1)*charheight-1;
    lineinc = -VIDS_ArmScreenWidthBytes;
  }

  if ( VID_PCbpp==8 )  /* 8 bytes per character width */
  {
    width <<= 3;
    left <<= 3;
  }
  else
  {
    width <<= 2;
    left <<= 2;
  }

  pDst = baseptr + dst_line*VIDS_ArmScreenWidthBytes +left;
  pSrc = baseptr + src_line*VIDS_ArmScreenWidthBytes +left;
  nlines = nlines * charheight;

  /* Full-width scroll-up optimisation */
  if ( width == lineinc && lineinc > 0 )
    memcpy ( pDst, pSrc, nlines * lineinc );
  else
    while ( nlines-- > 0 )
    {
      memcpy ( pDst, pSrc, width );
      pDst += lineinc;
      pSrc += lineinc;
    }
}

/* --------------------------------- */

static void TextMemoryFill ( BYTE *baseptr, int left, int width,
       int dst_line, int nlines, int attrib )
{
  register BYTE *pDst;
  register int i, lineinc;

  lineinc = VID_TextModeCols*8;

  /* Each character takes 8 framebuffer bytes */
  pDst = baseptr + (dst_line*VID_TextModeCols+left)*8;
  width = width*8;

  while ( nlines-- > 0 )
  {
    for (i=width-8; i>=0; i-=8)
    {
      pDst[i] = 0x20;     /* Fill with space */
      pDst[i+1] = attrib;
    }
    pDst+=lineinc;
  }

}

/* --------------------------------- */

static void CGAMemoryFill ( BYTE *baseptr, int left, int width,
       int dst_line, int nlines, int attrib )
{
  register BYTE *pDst;
  register int i;

  pDst = baseptr + (dst_line*320+left)*4;
  width = width*4;
  nlines = nlines * 4;
  attrib = FBAttrib(attrib);

  while ( nlines-- > 0 )
  {
    for (i=width-4; i>=0; i-=4)
    {
      pDst[i] = attrib;
      pDst[i+0x8000] = attrib;
    }
    pDst+=320;
  }
}

/* --------------------------------- */

static void GraphMemoryFill ( BYTE *baseptr, int left, int width,
       int dst_line, int nlines, int attrib, int charheight )
{
  register BYTE *pDst;

  if ( VID_PCbpp==8 )  /* 8 bytes per character width */
  {
    width <<= 3;
    left <<= 3;
  }
  else
  {
    width <<= 2;
    left <<= 2;
  }

  pDst = baseptr + dst_line*charheight*VIDS_ArmScreenWidthBytes +left;
  nlines = nlines * charheight;

  if ( width == VIDS_ArmScreenWidthBytes )
    memset ( pDst, attrib, width*nlines );
  else
    while ( nlines-- > 0 )
    {
      memset ( pDst, attrib, width );
      pDst += VIDS_ArmScreenWidthBytes;
    }
}


/* VID_Scroll ======================================================

   This is used as a speedup by the BIOS. 'top', 'left', 'bottom',
   'right', describe a text rectangle to be scrolled up or down.
   'attrib' is the value of the byte used to fill in the gaps.
   'charheight' is the height of each character (only needs to be
   valid in VGA modes. 'dist' is the number of characters up and
   down to be scrolled.

   All parameters are clipped to the available limits. It is
    possible to scroll off-screen data by giving large top and
    bottom values.

   'PCbase' is what the PC thinks its screen base address is (as
   derived from the BIOS mode variables). One or two programs clear
   non-screen areas of memory by setting the base address to something
   weird, calling the BIOS, then putting them back. Sick, eh?

   (Note that CGA45 mode is just the same as CGA6 mode if you imagine
   that the characters are twice as wide. We adjust left & right once
   early on to account for this).
*/

void VID_Scroll ( int top, int left, int bottom, int right,
                      int attrib, int charheight, int dist, int PCbase )
{
  int memstart, max_mem_line, max_screen_line, max_cols;
  int src_line,  dst_line, move_height;
  int fill_line, fill_height;
  int width;

  /* Is chain4 addressing mode in use? */
  if ( VGA_SeqRegs [ SEQ_MemMode ] & 8 )
    memstart = (PCbase & 0xFFFF);
  else
    memstart = (PCbase & 0xFFFF) << 2;

  /* Check maximum line which we can scroll */

  if ( VID_ModeType == MODE_TEXT )
  {
    charheight = VIDS_CharHeight;
    max_mem_line = (VGA_RAM_SIZE - memstart) / (8*VID_TextModeCols);
    max_screen_line = VID_TextModeRows;
    max_cols = VID_TextModeCols;
  }
  else if ( VID_ModeType & (MODE_CGA45|MODE_CGA6) )
  {
    charheight = 8;
    max_mem_line = (VGA_RAM_SIZE-memstart-0x8000) / (320*4);
    max_screen_line = VID_SpriteHeight >> 3;
    if ( VID_ModeType == MODE_CGA45 )
    {
      left = left<<1;
      right = (right<<1)+1;
    }
    max_cols = VID_PCXtotal>>3;
  }
  else if ( VID_ModeType & (MODE_VGA|MODE_256CLR) )
  {
    if ( charheight <= 0 )
      return;
    max_mem_line = (VGA_RAM_SIZE-memstart) /
                       (VIDS_ArmScreenWidthBytes*charheight);
    max_screen_line = VID_SpriteHeight / charheight;
    max_cols = VID_PCXtotal >> 3;
  }
  else /* Windows mode, VESA mode, or no mode. Do not scroll */
    return;

  /* Check for funny parameters */

  if ( bottom >= max_mem_line )
    bottom = max_mem_line-1;
  if ( right  >= max_cols )
    right = max_cols-1;

  if ( top < 0 || bottom < top || left < 0 || right < left )
    return;

  width = right-left+1;

  /* Sort out what we have to move & where */

  if ( dist > 0 )  /* Scroll up */
  {
    src_line = top+dist;
    dst_line = top;
    move_height = bottom-top+1-dist;
    fill_line = bottom-dist+1;
    fill_height = dist;
  }
  else  /* Scroll down */
  {
    src_line = bottom-(-dist);
    dst_line = bottom;
    move_height = bottom-top+1-(-dist);
    fill_line = top;
    fill_height = (-dist);
  }

  if ( dist == 0 || move_height <= 0 ) /* Fill only */
  {
    move_height = 0;
    fill_line = top;
    fill_height = bottom-top+1;
  }

  /* Hide text cursor, stops it from being scrolled */
  CUR_HideCursor();

  /* Move & fill framebuffer bytes ----- */

  if ( VID_ModeType == MODE_TEXT )
  {
    if ( move_height > 0 )
      TextMemoryMove ( VIDS_MemoryBlk+memstart, left, width,
                 src_line, dst_line, move_height );

    TextMemoryFill ( VIDS_MemoryBlk+memstart, left, width,
                 fill_line, fill_height, attrib );
  }
  else if ( VID_ModeType & (MODE_CGA45|MODE_CGA6) )
  {
    if ( move_height > 0 )
      CGAMemoryMove ( VIDS_MemoryBlk+memstart, left, width,
                 src_line, dst_line, move_height );

    CGAMemoryFill ( VIDS_MemoryBlk+memstart, left, width,
                 fill_line, fill_height, attrib );
  }
  else
  {
    if ( move_height > 0 )
      GraphMemoryMove ( VIDS_MemoryBlk+memstart, left, width,
                 src_line, dst_line, move_height, charheight );

    GraphMemoryFill ( VIDS_MemoryBlk+memstart, left, width,
        fill_line, fill_height, FBAttrib(attrib), charheight );
  }

  /* If no funny stuff is happening, we can scroll the screen image */

  if ( memstart == VIDS_DisplayStart && bottom < max_screen_line )
  {
    if ( SeparateScreenImage() )
    {
      if ( move_height > 0 )
        GraphMemoryMove ( VIDS_DrawPointer, left, width,
                   src_line, dst_line, move_height, charheight );

      GraphMemoryFill ( VIDS_DrawPointer, left, width,
          fill_line, fill_height, ScrnAttrib(attrib), charheight );
    }

    /* Update relevant pixels on screen */
    left    = (left  << 3);
    right   = (right << 3) + 7;
    top     = top * charheight;
    bottom  = (bottom+1)*charheight-1;
    dist    = dist * charheight;

    /* Optimisation for full-screen scroll */

    if ( left == 0 && top == 0 && right == (VID_PCXtotal-1) &&
          bottom == (VID_PCYtotal-1) && dist > 0 )
    {
      SYS_FEState.VertScroll += dist;
      VID_AreaChanged ( left, right,
            bottom-dist+1, bottom, 3 );
    }
    else
    {
      VID_AreaChanged ( left, right, top, bottom, 3 );
    }

  }
  else /* Something odd has been requested. Redraw the screen manually */
  {
    VID_RedrawRequest(5);
  }

}




/* VID_ClearBuffer() ================================================

   ClearBuffer is called to clear the video buffer; it should also
   clear the screen image of said buffer as well, but as it is only
   ever called by a mode change, this isn't actually necessary.
*/

void VID_ClearBuffer(void)
{
  register int i;
  register BYTE *dst;

  dst = VIDS_MemoryBlk;

  /* This is used as a speedup by the BIOS */
  switch ( VID_ModeType )
  {
    case MODE_TEXT:   /* Clear first half (16K characters) of video RAM */
      for ( i=0; i< 0x20000; i+= 8)
      {
        dst[i]   = 0x20;  /* Character = space */
        dst[i+1] = 0x07;  /* Attribute = normal */
      }
      break;

    case MODE_CGA45:  /* First 16K entries in plane 0 */
    case MODE_CGA6:
      for ( i=0; i< 0x10000; i+= 4)
        dst[i] = 0;
      break;

    case MODE_256CLR:
    case MODE_VGA:
      memset( dst, 0, VGA_RAM_SIZE );
      return;

    case MODE_VESA:
      memset ( dst, 0, VID_MemorySize );
      return;

    default:  /* Windows Mode has no 'clear' function */
      return;
  }

  if ( SeparateScreenImage() )
  {
    memset(VIDS_DrawPointer, 0, VID_SpriteHeight*VIDS_ArmScreenWidthBytes );
    VIDS_CursorVisible = false;
  }

}

