/*
 * conout.c - lowlevel color model dependent screen handling routines
 *
 *
 * Copyright (C) 2004 by Authors (see below)
 * Copyright (C) 2016 The EmuTOS development team
 *
 * Authors:
 *  MAD     Martin Doering
 *
 * This file is distributed under the GPL, version 2 or at your
 * option any later version.  See doc/license.txt for details.
 */

#include "conout.h"
#include "video.h"
#include "font.h"
#include "fb.h"
#include "bas_string.h"

#define  plane_offset   2       /* interleaved planes */

#define v_fnt_st fnt->first_ade
#define v_fnt_nd fnt->last_ade
#define v_off_ad fnt->off_table
#define v_bas_ad ((uint8_t *) info_fb->screen_base)

uint8_t *v_cur_ad;              /* cursor address */
int8_t v_stat_0;                /* console status byte */
uint16_t v_cur_cx;
uint16_t v_cur_cy;
int8_t v_cur_tim;
uint16_t v_cel_mx;
uint16_t v_cel_my;
uint16_t v_cel_wr;
int16_t v_cur_of;
uint16_t v_cel_ht;
int8_t v_period;
const uint16_t *v_fnt_ad;
int16_t v_fbt_wr;
int16_t v_col_bg;
int16_t v_col_fg;
uint16_t v_fnt_wr;


#define v_planes (info_fb->var.bits_per_pixel)
extern struct fb_info *info_fb;
#define v_lin_wr (info_fb->var.width / 2) /* length of a screen line in words */

/*
 * internal prototypes
 */
static void neg_cell(uint8_t *);
static uint8_t * cell_addr(int, int);
static void cell_xfer(uint8_t *, uint8_t *);
static bool next_cell(void);


/*
 * char_addr - retrieve the address of the source cell
 *
 *
 * Given an offset value.
 *
 * in:
 *   ch - source cell code
 *
 * out:
 *   pointer to first byte of source cell if code was valid
 */

static uint8_t *char_addr(int16_t ch)
{
    uint16_t offs;

    /* test against limits */
    if (ch >= v_fnt_st)
    {
        if (ch <= v_fnt_nd)
        {
            /* getch offset from offset table */
            offs = v_off_ad[ch];
            offs >>= 3;                 /* convert from pixels to bytes. */

            /* return valid address */
            return (uint8_t*) v_fnt_ad + offs;
        }
    }

    /* invalid code. no address returned */
    return NULL;
}



/*
 * ascii_out - prints an ascii character on the screen
 *
 * in:
 *
 * ch.w      ascii code for character
 */

void ascii_out(int ch)
{
    uint8_t * src, * dst;
    bool visible;                       /* was the cursor visible? */

    src = char_addr(ch);                /* a0 -> get character source */
    if (src == NULL)
        return;                         /* no valid character */

    dst = v_cur_ad;                     /* a1 -> get destination */

    visible = v_stat_0 & M_CVIS;        /* test visibility bit */
    if ( visible ) {
        neg_cell(v_cur_ad);             /* delete cursor. */
        v_stat_0 &= ~M_CVIS;                    /* start of critical section */
    }

    /* put the cell out (this covers the cursor) */
    cell_xfer(src, dst);

    /* advance the cursor and update cursor address and coordinates */
    if (next_cell())
    {
        uint8_t * cell;

        int y = v_cur_cy;

        /* perform cell carriage return. */
        cell = v_bas_ad + (uint32_t) v_cel_wr * y;
        v_cur_cx = 0;                   /* set X to first cell in line */

        /* perform cell line feed. */
        if ( y < v_cel_my )
        {
            cell += v_cel_wr;           /* move down one cell */
            v_cur_cy = y + 1;           /* update cursor's y coordinate */
        }
        else {
            scroll_up(0);               /* scroll from top of screen */
        }
        v_cur_ad = cell;                /* update cursor address */
    }

    /* if visible */
    if ( visible ) {
        neg_cell(v_cur_ad);             /* display cursor. */
        v_stat_0 |= M_CSTATE;           /* set state flag (cursor on). */
        v_stat_0 |= M_CVIS;             /* end of critical section. */

        /* do not flash the cursor when it moves */
        if (v_stat_0 & M_CFLASH) {
            v_cur_tim = v_period;       /* reset the timer. */
        }
    }
}




/*
 * blank_out - Fills region with the background color.
 *
 * Fills a cell-word aligned region with the background color.
 *
 * The rectangular region is specified by a top/left cell x,y and a
 * bottom/right cell x,y, inclusive.  Routine assumes top/left x is
 * even and bottom/right x is odd for cell-word alignment. This is,
 * because this routine is heavily optimized for speed, by always
 * blanking as much space as possible in one go.
 *
 * in:
 *   topx - top/left cell x position (must be even)
 *   topy - top/left cell y position
 *   botx - bottom/right cell x position (must be odd)
 *   boty - bottom/right cell y position
 */

void blank_out(int topx, int topy, int botx, int boty)
{

}



/*
 * cell_addr - convert cell X,Y to a screen address.
 *
 *
 * convert cell X,Y to a screen address. also clip cartesian coordinates
 * to the limits of the current screen.
 *
 * latest update:
 *
 * 18-sep-84
 * in:
 *
 * d0.w      cell X
 * d1.w      cell Y
 *
 * out:
 * a1      points to first byte of cell
 */

static uint8_t *cell_addr(int x, int y)
{
    int32_t disx, disy;

    /* check bounds against screen limits */
    if ( x >= v_cel_mx )
        x = v_cel_mx;           /* clipped x */

    if ( y >= v_cel_my )
        y = v_cel_my;           /* clipped y */

    /* X displacement = even(X) * v_planes + Xmod2 */
    disx = (int32_t)v_planes * (x & ~1);
    if ( x & 1 ) {              /* Xmod2 = 0 ? */
        disx++;                 /* Xmod2 = 1 */
    }

    /* Y displacement = Y // cell conversion factor */
    disy = (int32_t)v_cel_wr * y;

    /*
     * cell address = screen base address + Y displacement
     * + X displacement + offset from screen-begin (fix)
     */
    return v_bas_ad + disy + disx + v_cur_of;
}



/*
 * cell_xfer - Performs a byte aligned block transfer.
 *
 *
 * This routine performs a byte aligned block transfer for the purpose of
 * manipulating monospaced byte-wide text. the routine maps a single-plane,
 * arbitrarily-long byte-wide image to a multi-plane bit map.
 * all transfers are byte aligned.
 *
 * in:
 * a0.l      points to contiguous source block (1 byte wide)
 * a1.l      points to destination (1st plane, top of block)
 *
 * out:
 * a4      points to byte below this cell's bottom
 */

static void cell_xfer(uint8_t * src, uint8_t * dst)
{
    uint8_t * src_sav, * dst_sav;
    uint16_t fg;
    uint16_t bg;
    int fnt_wr, line_wr;
    int plane;

    fnt_wr = v_fnt_wr;
    line_wr = v_lin_wr;

    /* check for reversed foreground and background colors */
    if ( v_stat_0 & M_REVID)
    {
        fg = v_col_bg;
        bg = v_col_fg;
    }
    else
    {
        fg = v_col_fg;
        bg = v_col_bg;
    }

    src_sav = src;
    dst_sav = dst;

    for (plane = v_planes; plane--;)
    {
        int i;

        src = src_sav;                  /* reload src */
        dst = dst_sav;                  /* reload dst */

        if (bg & 0x0001)
        {
            if (fg & 0x0001)
            {
                /* back:1  fore:1  =>  all ones */
                for (i = v_cel_ht; i--;)
                {
                    *dst = 0xff;                /* inject a block */
                    dst += line_wr;
                }
            }
            else
            {
                /* back:1  fore:0  =>  invert block */
                for (i = v_cel_ht; i--; )
                {
                    /* inject the inverted source block */
                    *dst = ~*src;
                    dst += line_wr;
                    src += fnt_wr;
                }
            }
        }
        else {
            if (fg & 0x0001)
            {
                /* back:0  fore:1  =>  direct substitution */
                for (i = v_cel_ht; i--;)
                {
                    *dst = *src;
                    dst += line_wr;
                    src += fnt_wr;
                }
            }
            else
            {
                /* back:0  fore:0  =>  all zeros */
                for (i = v_cel_ht; i--; )
                {
                    *dst = 0x00;                /* inject a block */
                    dst += line_wr;
                }
            }
        }

        bg >>= 1;                       /* next background color bit */
        fg >>= 1;                       /* next foreground color bit */
        dst_sav += plane_offset;        /* top of block in next plane */
    }
}



/*
 * move_cursor - move the cursor.
 *
 * move the cursor and update global parameters
 * erase the old cursor (if necessary) and draw new cursor (if necessary)
 *
 * in:
 * d0.w    new cell X coordinate
 * d1.w    new cell Y coordinate
 */

void move_cursor(int x, int y)
{
    /* update cell position */

    /* clamp x,y to valid ranges */
    if (x < 0)
        x = 0;
    else if (x > v_cel_mx)
        x = v_cel_mx;

    if (y < 0)
        y = 0;
    else if (y > v_cel_my)
        y = v_cel_my;

    v_cur_cx = x;
    v_cur_cy = y;

    /* is cursor visible? */
    if ( !(v_stat_0 & M_CVIS) ) {
        /* not visible */
        v_cur_ad = cell_addr(x, y);             /* just set new coordinates */
        return;                                 /* and quit */
    }

    /* is cursor flashing? */
    if ( v_stat_0 & M_CFLASH ) {
        v_stat_0 &= ~M_CVIS;                    /* yes, make invisible...semaphore. */

        /* is cursor presently displayed ? */
        if ( !(v_stat_0 & M_CSTATE )) {
            /* not displayed */
            v_cur_ad = cell_addr(x, y);         /* just set new coordinates */

            /* show the cursor when it moves */
            neg_cell(v_cur_ad);                 /* complement cursor. */
            v_stat_0 |= M_CSTATE;
            v_cur_tim = v_period;               /* reset the timer. */

            v_stat_0 |= M_CVIS;                 /* end of critical section. */
            return;
        }
    }

    /* move the cursor after all special checks failed */
    neg_cell(v_cur_ad);                         /* erase present cursor */

    v_cur_ad = cell_addr(x, y);                 /* fetch x and y coords. */
    neg_cell(v_cur_ad);                         /* complement cursor. */

    /* do not flash the cursor when it moves */
    v_cur_tim = v_period;                       /* reset the timer. */

    v_stat_0 |= M_CVIS;                         /* end of critical section. */
}



/*
 * neg_cell - negates
 *
 * This routine negates the contents of an arbitrarily-tall byte-wide cell
 * composed of an arbitrary number of (Atari-style) bit-planes.
 * Cursor display can be accomplished via this procedure.  Since a second
 * negation restores the original cell condition, there is no need to save
 * the contents beneath the cursor block.
 *
 * in:
 * a1.l      points to destination (1st plane, top of block)
 *
 * out:
 */

static void neg_cell(uint8_t * cell)
{
    int plane, len;
    int cell_len = v_cel_ht;

    v_stat_0 |= M_CRIT;                 /* start of critical section. */

    for (plane = v_planes; plane--; ) {
        uint8_t * addr = cell;            /* top of current dest plane */

        /* reset cell length counter */
        for (len = cell_len; len--; ) {
            *addr = ~*addr;
            addr += v_lin_wr;
        }
        cell += plane_offset;           /* a1 -> top of block in next plane */
    }
    v_stat_0 &= ~M_CRIT;                /* end of critical section. */
}



/*
 * invert_cell - negates the cells bits
 *
 * This routine negates the contents of an arbitrarily-tall byte-wide cell
 * composed of an arbitrary number of (Atari-style) bit-planes.
 *
 * Wrapper for neg_cell().
 *
 * in:
 * x - cell X coordinate
 * y - cell Y coordinate
 */

void invert_cell(int x, int y)
{
    /* fetch x and y coords and invert cursor. */
    neg_cell(cell_addr(x, y));
}



/*
 * next_cell - Return the next cell address.
 *
 * sets next cell address given the current position and screen constraints
 *
 * returns:
 *     false - no wrap condition exists
 *     true  - CR LF required (position has not been updated)
 */

static bool next_cell(void)
{
    /* check bounds against screen limits */
    if (v_cur_cx == v_cel_mx)
    {
        /* increment cell ptr */
        if (!(v_stat_0 & M_CEOL))
        {
            /* overwrite in effect */
            return 0;                   /* no wrap condition exists */
                                        /* don't change cell parameters */
        }

        /* call carriage return routine */
        /* call line feed routine */
        return 1;                       /* indicate that CR LF is required */
    }

    v_cur_cx += 1;                      /* next cell to right */

    /* if X is even, move to next word in the plane */
    if (v_cur_cx & 1)
    {
        /* x is odd */
        v_cur_ad += 1;                  /* a1 -> new cell */
        return 0;                       /* indicate no wrap needed */
    }

    /* new cell (1st plane), added offset to next word in plane */
    v_cur_ad += (v_planes << 1) - 1;

    return 0;                           /* indicate no wrap needed */
}


/*
 * scroll_up - Scroll upwards
 *
 *
 * Scroll copies a source region as wide as the screen to an overlapping
 * destination region on a one cell-height offset basis.  Two entry points
 * are provided:  Partial-lower scroll-up, partial-lower scroll-down.
 * Partial-lower screen operations require the cell y # indicating the
 * top line where scrolling will take place.
 *
 * After the copy is performed, any non-overlapping area of the previous
 * source region is "erased" by calling blank_out which fills the area
 * with the background color.
 *
 * in:
 *   top_line - cell y of cell line to be used as top line in scroll
 */

void scroll_up(int top_line)
{
    uint32_t count;
    uint8_t * src, * dst;

    /* screen base addr + cell y nbr * cell wrap */
    dst = v_bas_ad + (uint32_t) top_line * v_cel_wr;

    /* form source address from cell wrap + base address */
    src = dst + v_cel_wr;

    /* form # of bytes to move */
    count = (uint32_t) v_cel_wr * (v_cel_my - top_line);

    /* move BYTEs of memory*/
    memmove(dst, src, count);

    /* exit thru blank out, bottom line cell address y to top/left cell */
    blank_out(0, v_cel_my , v_cel_mx, v_cel_my );
}



/*
 * scroll_down - Scroll (partially) downwards
 */

void scroll_down(int start_line)
{
    uint32_t count;
    uint8_t * src, * dst;

    /* screen base addr + offset of start line */
    src = v_bas_ad + (uint32_t) start_line * v_cel_wr;

    /* form destination from source + cell wrap */
    dst = src + v_cel_wr;

    /* form # of bytes to move */
    count = (uint32_t) v_cel_wr * (v_cel_my - start_line);

    /* move BYTEs of memory*/
    memmove(dst, src, count);

    /* exit thru blank out */
    blank_out(0, start_line , v_cel_mx, start_line);
}

