/*
    Module:     ZG_LwLvl    (ZipGraph Low-Level functions)
    Version     1.10        01-Nov-1989

    Language:   ANSI C w/MS-DOS extensions
    Environ:    IBM-PC compatibles w/MDA, CGA, HGC, EGA, MCGA, or VGA

    Compilers:  Borland Turbo C v2.00
                Lattice C v6.01
                Microsoft C v5.10 and QuickC v2.01
                Zortech C & C++ v2.01

    Purpose:    Provides low-level graphics routines for detecting graphics
                adapters, setting graphics modes, and plotting pixels. This
                module is required by the higher-level ZipGraph modules.

    Written by: Scott Robert Ladd
                705 West Virginia
                Gunnison CO 81230

                BBS (303)641-6438
                FidoNet 1:104/708
*/

#define __ZG_LWLVL_C 1

#if !defined(LATTICE)
    #include "conio.h"
#endif

#include "dos.h"
#include "zg_lwlvl.h"

/*-----------------------------
  handle compiler differences
-----------------------------*/

#if defined(_MSC) || defined(_QC) || defined(__ZTC__) || defined(LATTICE)
    #define OUT_PORT(port,val) outp(port,val)
#elif defined(__TURBOC__)
    #define OUT_PORT(port,val) outportb(port,val)
#endif

/*--------------------------
  pointer conversion macro
--------------------------*/

#if !defined(MK_FP)
    #define MK_FP(seg,off) ((void far *)(((long)(seg) << 16)|(unsigned)(off)))
#endif

/*------------------------------
  ZipGraph INTERNAL PROTOTYPES
------------------------------*/

static void DummyPlot(int x, int y, int color);
static int  DummyRead(int x, int y);

static void CGA1_Plot(int x, int y, int color);
static void CGA2_Plot(int x, int y, int color);
static void MCGA_Plot(int x, int y, int color);
static void M256_Plot(int x, int y, int color);
static void EVGA_Plot(int x, int y, int color);

static int  CGA1_Read(int x, int y);
static int  CGA2_Read(int x, int y);
static int  MCGA_Read(int x, int y);
static int  M256_Read(int x, int y);
static int  EVGA_Read(int x, int y);

static void HGC_GraphMode(void);
static void HGC_TextMode(void);
static void HGC_Plot(int x, int y, int color);
static int  HGC_Read(int x, int y);

/*---------------------------
  ZipGraph PUBLIC VARIABLES
---------------------------*/

struct
    {
    int     Type;
    int     Monitor;
    int     Mode;

    int     Xwidth;
    int     Ylength;
    int     NoColors;
    }
    ZG_VideoInfo;

void (* ZG_PlotPixel)(int x, int y, int color) = DummyPlot;
int  (* ZG_ReadPixel)(int x, int y) = DummyRead;

/*----------------------------------------
  ZipGraph INTERNAL TABLES and VARIABLES
----------------------------------------*/

static const struct
    {
    int BestRes;
    int MostColor;

    unsigned long ModeList;
    }
    VideoTable[06] =
        {
        { 0,                 0,                  0x00000000L},
        { ZG_MOD_640x200x2,  ZG_MOD_320x200x4,   0x00000006L},
        { ZG_MOD_720x348x2,  ZG_MOD_720x348x2,   0x00000001L},
        { ZG_MOD_640x350x16, ZG_MOD_640x350x16,  0x0000007EL},
        { ZG_MOD_640x480x2,  ZG_MOD_320x200x256, 0x00000486L},
        { ZG_MOD_640x480x16, ZG_MOD_320x200x256, 0x000003FEL}
        };

static const struct
    {
    char ActualMode;
    void (* PixelProc)(int x, int y, int color);
    int  (* ReadProc)(int x, int y);
    int Xwidth;
    int Ylength;
    int NoColors;
    }
    ModeData[10] =
        {
        { 0x00, HGC_Plot,  HGC_Read,  720, 348, 2  },
        { 0x04, CGA2_Plot, CGA2_Read, 320, 200, 4  },
        { 0x06, CGA1_Plot, CGA1_Read, 640, 200, 2  },
        { 0x0D, MCGA_Plot, MCGA_Read, 320, 200, 16 },
        { 0x0E, EVGA_Plot, EVGA_Read, 640, 200, 16 },
        { 0x0F, EVGA_Plot, EVGA_Read, 640, 350, 4  },
        { 0x10, EVGA_Plot, EVGA_Read, 640, 350, 16 },
        { 0x11, EVGA_Plot, EVGA_Read, 640, 480, 2  },
        { 0x12, EVGA_Plot, EVGA_Read, 640, 480, 16 },
        { 0x13, M256_Plot, M256_Read, 320, 200, 256}
        };

static unsigned char OriginalMode = 0;

static int PixelMode = ZG_PXL_SET;

static int ZG_Inited = 0;

/*------------------------------
  IBM-type adapter global data
------------------------------*/

#define CGA_VID_SEG  0xB800
#define EVGA_VID_SEG 0xA000

/*-------------------------
  MDA and HGC global data
-------------------------*/

/* Hercules video RAM segment */
#define HGC_VID_SEG  0xB000

/* Monochrome 6845 Video Controller ports */
#define HGC_IDX_PORT 0x03B4
#define HGC_DAT_PORT 0x03B5
#define HGC_CTL_PORT 0x03B8
#define HGC_CFG_PORT 0x03BF

/*----------------------------
  ZipGraph PUBLIC FUNCTIONS!
----------------------------*/

int ZG_Init(void)
    {
    union REGS regs;
    int i, status_changed;
    unsigned char orig_status;

    ZG_VideoInfo.Type    = ZG_ERROR;
    ZG_VideoInfo.Monitor = ZG_ERROR;
    ZG_VideoInfo.Mode    = ZG_ERROR;

    ZG_VideoInfo.Xwidth   = 0;
    ZG_VideoInfo.Ylength  = 0;
    ZG_VideoInfo.NoColors = 0;

    regs.h.ah = 0x1A; /* VGA Identifier Adapter Service */
    regs.h.al = 0;
    int86(0x10,&regs,&regs);

    if (regs.h.al == 0x1A)
        {
        switch (regs.h.bl)
            {
            case 1 : ZG_VideoInfo.Type    = ZG_VID_MDA;
                     ZG_VideoInfo.Monitor = ZG_MTR_MONO;
                     break;

            case 2 : ZG_VideoInfo.Type    = ZG_VID_CGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_COLOR;
                     break;

            case 4 : ZG_VideoInfo.Type    = ZG_VID_EGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_COLOR;
                     break;

            case 5 : ZG_VideoInfo.Type    = ZG_VID_EGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_MONO;
                     break;

            case 7 : ZG_VideoInfo.Type    = ZG_VID_VGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_MONO;
                     break;

            case 8 : ZG_VideoInfo.Type    = ZG_VID_VGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_COLOR;
                     break;

            case 10: ZG_VideoInfo.Type    = ZG_VID_MCGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_COLOR;
                     break;

            case 11: ZG_VideoInfo.Type    = ZG_VID_MCGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_MONO;
                     break;

            case 12: ZG_VideoInfo.Type    = ZG_VID_MCGA;
                     ZG_VideoInfo.Monitor = ZG_MTR_COLOR;
                     break;

            default:
                     return 1;
            }
        }
    else
        {
        /* check for an EGA */
        regs.h.ah = 0x12;
        regs.x.bx = 0x0010;
        int86(0x10,&regs,&regs);

        if (regs.x.bx != 0x10)
            {
            ZG_VideoInfo.Type = ZG_VID_EGA;

            regs.h.ah = 0x12;
            regs.x.bx = 0x0010;
            int86(0x10,&regs,&regs);

            if (regs.h.bh == 0)
                ZG_VideoInfo.Monitor = ZG_MTR_COLOR;
            else
                ZG_VideoInfo.Monitor = ZG_MTR_MONO;
            }
        else
            {
            int86(0x11,&regs,&regs);

            switch ((regs.h.al & 0x30) >> 4)
                {
                case 0 :
                    return 1;

                case 1 :
                case 2 :
                    ZG_VideoInfo.Type    = ZG_VID_CGA;
                    ZG_VideoInfo.Monitor = ZG_MTR_COLOR;
                    break;

                case 3 :
                    status_changed = 0;

                    orig_status = (unsigned char)(inp(0x03BA) & 0x80);

                    for (i = 0; (i < 30000) && (!status_changed); ++i)
                        if (orig_status != (unsigned char)(inp(0x03BA) & 0x80))
                            status_changed = 1;

                    if (status_changed)
                        ZG_VideoInfo.Type = ZG_VID_HGC;
                    else
                        ZG_VideoInfo.Type = ZG_VID_MDA;

                    ZG_VideoInfo.Monitor  = ZG_MTR_MONO;
                }
            }
        }

    if (ZG_VideoInfo.Type != ZG_VID_HGC)
        {
        regs.h.ah = 0x0F;
        int86(0x10,&regs,&regs);
        OriginalMode = regs.h.al;
        }

    ZG_Inited = 1;

    return 0;
    }

int ZG_SetMode(int VideoMode)
    {
    union REGS regs;

    if (VideoMode < 0)
        return 1;

    if (VideoMode == ZG_MOD_BESTRES)
        VideoMode = VideoTable[ZG_VideoInfo.Type].BestRes;
    else
        if (VideoMode == ZG_MOD_MOSTCOLOR)
            VideoMode = VideoTable[ZG_VideoInfo.Type].MostColor;

    if ((VideoMode > ZG_MOD_320x200x256)
    || (!(VideoTable[ZG_VideoInfo.Type].ModeList & (1 << VideoMode))))
        return 1;

    ZG_PlotPixel = ModeData[VideoMode].PixelProc;
    ZG_ReadPixel = ModeData[VideoMode].ReadProc;

    if (VideoMode == ZG_MOD_720x348x2)
        HGC_GraphMode();
    else
        {
        regs.h.ah = 0;
        regs.h.al = ModeData[VideoMode].ActualMode;
        int86(0x10,&regs,&regs);
        }

    ZG_VideoInfo.Mode     = VideoMode;
    ZG_VideoInfo.Xwidth   = ModeData[VideoMode].Xwidth;
    ZG_VideoInfo.Ylength  = ModeData[VideoMode].Ylength;
    ZG_VideoInfo.NoColors = ModeData[VideoMode].NoColors;

    return 0;
    }

int ZG_Done(void)
    {
    union REGS regs;

    if (!ZG_Inited)
        return 1;

    if (ZG_VideoInfo.Type == ZG_VID_HGC)
        HGC_TextMode();
    else
        {
        regs.h.ah = 0;
        regs.h.al = OriginalMode;
        int86(0x10,&regs,&regs);
        }

    return 0;
    }

int ZG_SetPixelMode(int PixMode)
    {
    if ((PixMode < ZG_PXL_SET) || (PixMode > ZG_PXL_XOR))
        return 1;

    PixelMode = PixMode;

    return 0;
    }

int ZG_SetCGAPalette(char PaletteNo)
    {
    union REGS regs;

    if (ZG_VideoInfo.Type != ZG_VID_CGA)
        return 1;

    regs.h.ah = 0x0B;
    regs.h.bl = PaletteNo;
    regs.h.bh = 0x01;
    int86(0x10,&regs,&regs);

    return 0;
    }

int ZG_SetEVGAPalette(char Palette, char Color)
    {
    union REGS regs;

    if ((ZG_VideoInfo.Type != ZG_VID_EGA) && (ZG_VideoInfo.Type != ZG_VID_VGA))
        return 1;

    regs.h.ah = 0x10;
    regs.h.al = 0x00;
    regs.h.bh = Color;
    regs.h.bl = Palette;
    int86(0x10,&regs,&regs);

    return 0;
    }

/*--------------------------------
  Dummy pixel plotter and reader
--------------------------------*/

static void DummyPlot(int x, int y, int color)
    {
    /* it does nothing */
    }

static int DummyRead(int x, int y)
    {
    /* it does nothing */
    return 0;
    }

/*---------------------------------------------
  CGA, EGA, MCGA, VGA pixel plotting functions
---------------------------------------------*/

static void CGA1_Plot(int x, int y, int color)
    {
    /* this routine used for CGA 640x200x2 mode */
    unsigned int pixel_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(CGA_VID_SEG, 0x2000 * (y&1) + 0x50 * (y/2) + (x >> 3));

    /* calculate bit mask */
    pixel_mask = 0x80 >> (x & 7);

    /* set pixel */
    switch (PixelMode)
        {
        case ZG_PXL_SET :
            if (color)
                *pixel_byte |= pixel_mask;
            else
                *pixel_byte &= ~pixel_mask;
            break;

        case ZG_PXL_AND :
            if (color)
                *pixel_byte &= pixel_mask;
            break;

        case ZG_PXL_OR  :
            if (!color)
                *pixel_byte |= pixel_mask;
            break;

        case ZG_PXL_XOR :
            if (!color)
                *pixel_byte ^= pixel_mask;
        }
    }

static void CGA2_Plot(int x, int y, int color)
    {
    /* this routine used for CGA 320x200x4 mode */
    unsigned int pixel_mask, alt_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(CGA_VID_SEG,0x2000 * (y & 1) + 80 * (y >> 1) + (x >> 2));
    pixel_mask = (color & 3) << (6 - ((x & 3) << 1));

    switch (PixelMode)
        {
        case ZG_PXL_SET :
            /* clear the appropriate bits */
            alt_mask     = 0xC0 >> ((x & 3) << 1);
            *pixel_byte &= ~alt_mask;

            /* insert the bits for the specified color */
            *pixel_byte |= pixel_mask;
            break;

        case ZG_PXL_AND :
            *pixel_byte &= pixel_mask;
            break;

        case ZG_PXL_OR :
            *pixel_byte |= pixel_mask;
            break;

        case ZG_PXL_XOR :
            *pixel_byte ^= pixel_mask;
        }
    }

static void MCGA_Plot(int x, int y, int color)
    {
    /* this routine used for MCGA/VGA 320x200 16-color mode */
    unsigned char pixel_mask;
    volatile unsigned char dummy;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(EVGA_VID_SEG, (y * 40) + (x >> 3));

    /* set up mask */
    pixel_mask = (char)(0x80 >> (x & 7));

    /* set-up video controller */
    OUT_PORT(0x03CE, 8);
    OUT_PORT(0x03CF, pixel_mask);

    OUT_PORT(0x03CE, 3);
    OUT_PORT(0x03CF, (char)(PixelMode << 3));

    OUT_PORT(0x03C4, 2);
    OUT_PORT(0x03C5, 0x0F);

    /* do a dummy read to load latches */
    dummy = *pixel_byte;

    /* clear latches */
    *pixel_byte = 0;

    /* set bit planes */
    OUT_PORT(0x03C4, 2);
    OUT_PORT(0x03C5, (char)color);

    *pixel_byte = 0xFF;

    /* finish up */
    OUT_PORT(0x03C4, 2);
    OUT_PORT(0x03C5, 0x0F);

    OUT_PORT(0x03CE, 3);
    OUT_PORT(0x03CF, 0);

    OUT_PORT(0x03CE, 8);
    OUT_PORT(0x03CF, 0xFF);
    }

static void M256_Plot(int x, int y, int color)
    {
    /* this routine used for MCGA/VGA 320x200x256 mode */
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(EVGA_VID_SEG, (y << 8) + (y << 6) + x);

    /* set pixel */
    switch (PixelMode)
        {
        case ZG_PXL_SET :
            *pixel_byte  = (unsigned char)color;
            break;
        case ZG_PXL_AND :
            *pixel_byte &= (unsigned char)color;
            break;
        case ZG_PXL_OR  :
            *pixel_byte |= (unsigned char)color;
            break;
        case ZG_PXL_XOR :
            *pixel_byte ^= (unsigned char)color;
        }
    }

static void EVGA_Plot(int x, int y, int color)
    {
    /* this routine used for all EGA/VGA modes expect 256-color modes */
    unsigned char pixel_mask;
    volatile unsigned char dummy;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(EVGA_VID_SEG, (y * 80) + (x >> 3));

    /* set up mask */
    pixel_mask = (char)(0x80 >> (x & 7));

    /* set-up video controller */
    OUT_PORT(0x03CE, 8);
    OUT_PORT(0x03CF, pixel_mask);

    OUT_PORT(0x03CE, 3);
    OUT_PORT(0x03CF, (char)(PixelMode << 3));

    OUT_PORT(0x03C4, 2);
    OUT_PORT(0x03C5, 0x0F);

    /* do a dummy read to load latches */
    dummy = *pixel_byte;

    /* clear latches */
    *pixel_byte = 0;

    /* set bit planes */
    OUT_PORT(0x03C4, 2);
    OUT_PORT(0x03C5, (char)color);

    *pixel_byte = 0xFF;

    /* finish up */
    OUT_PORT(0x03C4, 2);
    OUT_PORT(0x03C5, 0x0F);

    OUT_PORT(0x03CE, 3);
    OUT_PORT(0x03CF, 0);

    OUT_PORT(0x03CE, 8);
    OUT_PORT(0x03CF, 0xFF);
    }

/*---------------------------------------------
  CGA, EGA, MCGA, VGA pixel reading functions
---------------------------------------------*/

static int CGA1_Read(int x, int y)
    {
    /* this routine used for CGA 640x200x2 mode */
    unsigned int pixel_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(CGA_VID_SEG,0x2000 * (y & 3) + 80 * (y >> 1) + (x >> 3));

    /* calculate bit mask */
    pixel_mask = 0x80 >> (x & 7);

    /* read pixel */
    if (pixel_mask & *pixel_byte)
        return 1;
    else
        return 0;
    }

static int CGA2_Read(int x, int y)
    {
    /* this routine used for CGA 320x200x4 mode */
    unsigned int pixel_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(CGA_VID_SEG,0x2000 * (y & 1) + 80 * (y >> 1) + (x >> 2));
    pixel_mask = 3 << (6 - ((x & 3) << 1));

    return (int)((*pixel_byte & pixel_mask) >> (6 - ((x & 3) << 1)));
    }

static int MCGA_Read(int x, int y)
    {
    /* this routine used for all EGA/VGA modes expect 256-color modes */
    char i;
    int color = 0;
    unsigned int pixel_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(EVGA_VID_SEG, (y * 40) + (x >> 3));

    /* set up mask */
    pixel_mask = 0x80 >> (x & 7);

    /* read the color bits */
    for (i = 0; i < 4; ++i)
        {
        OUT_PORT(0x3CE, 4);
        OUT_PORT(0x3CF, i);

        OUT_PORT(0x3CE, 5);
        OUT_PORT(0x3CF, 0);

        if (*pixel_byte & pixel_mask)
            color |= 1 << i;
        }

    return color;
    }

static int M256_Read(int x, int y)
    {
    /* this routine used for MCGA/VGA 320x200x256 mode */
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(EVGA_VID_SEG, (y << 8) + (y << 6) + x);

    /* return pixel value */
    return *pixel_byte;
    }

static int EVGA_Read(int x, int y)
    {
    /* this routine used for all EGA/VGA modes expect 256-color modes */
    char i;
    int color = 0;
    unsigned int pixel_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(EVGA_VID_SEG, (y * 80 + (x >> 3)));

    /* set up mask */
    pixel_mask = 0x80 >> (x & 7);

    /* read the color bits */
    for (i = 0; i < 4; ++i)
        {
        OUT_PORT(0x3CE, 4);
        OUT_PORT(0x3CF, i);

        OUT_PORT(0x3CE, 5);
        OUT_PORT(0x3CF, 0);

        if (*pixel_byte & pixel_mask)
            color |= 1 << i;
        }

    return color;
    }

/*---------------
  HGC functions
---------------*/

static void HGC_GraphMode(void)
    {
    /* Codes to be loaded to Hercules 6845 to set graphics mode */
    static const unsigned char HGC_GRAPH_CODES[12] =
        {0x35,0x2D,0x2E,0x07,0x5B,0x02,0x57,0x57,0x02,0x03,0x00,0x00};

    unsigned int  i;
    unsigned char far * screen;

    OUT_PORT(HGC_CFG_PORT,3);

    screen = MK_FP(HGC_VID_SEG,0);

    OUT_PORT(HGC_CTL_PORT,2); /* set control port */

    for (i = 0; i < sizeof(HGC_GRAPH_CODES); ++i)
        {
        OUT_PORT(HGC_IDX_PORT, (char)i);
        OUT_PORT(HGC_DAT_PORT, HGC_GRAPH_CODES[i]);
        }

    for (i = 0; i <= 32767; ++i)
        {
        *screen = '\x00';
        ++screen;
        }

    OUT_PORT(HGC_CTL_PORT,10); /* set control port */
    }

static void HGC_TextMode(void)
    {
    /* Codes to be loaded to Hercules 6845 to set text mode */
    static const unsigned char HGC_TEXT_CODES[12]  =
        {0x61,0x50,0x52,0x0F,0x19,0x06,0x19,0x19,0x02,0x0D,0x0B,0x0C};

    unsigned int  i;
    unsigned char far * screen;

    OUT_PORT(HGC_CFG_PORT,3);

    screen = MK_FP(HGC_VID_SEG,0);

    OUT_PORT(HGC_CTL_PORT,0x20); /* set control port */

    for (i = 0; i < sizeof(HGC_TEXT_CODES); ++i)
        {
        OUT_PORT(HGC_IDX_PORT, (char)i);
        OUT_PORT(HGC_DAT_PORT, HGC_TEXT_CODES[i]);
        }

    for (i = 0; i <= 32767; ++i)
        {
        if (i % 2 == 1)
            *screen = '\x07';
        else
            *screen = '\x20';

        ++screen;
        }

    OUT_PORT(HGC_CTL_PORT,0x28); /* set control port */
    }

static void HGC_Plot(int x, int y, int color)
    {
    /* this routine used for HGC 720x348x2 mode */
    unsigned int pixel_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(HGC_VID_SEG,0x2000 * (y & 3) + 90 * (y >> 2) + (x >> 3));

    /* calculate bit mask */
    pixel_mask = 0x80 >> (x & 7);

    /* set pixel */
    switch (PixelMode)
        {
        case ZG_PXL_SET :
            if (color)
                *pixel_byte |= pixel_mask;
            else
                *pixel_byte &= ~pixel_mask;
            break;

        case ZG_PXL_AND :
            if (color)
                *pixel_byte &= pixel_mask;
            break;

        case ZG_PXL_OR  :
            if (!color)
                *pixel_byte |= pixel_mask;
            break;

        case ZG_PXL_XOR :
            if (!color)
                *pixel_byte ^= pixel_mask;
        }
    }

static int HGC_Read(int x, int y)
    {
    /* this routine used for HGC 720x348x2 mode */
    unsigned int pixel_mask;
    unsigned char far * pixel_byte;

    /* find the byte containing our pixel */
    pixel_byte = MK_FP(HGC_VID_SEG,0x2000 * (y & 3) + 90 * (y >> 2) + (x >> 3));

    /* calculate bit mask */
    pixel_mask = 0x80 >> (x & 7);

    /* read pixel */
    if (pixel_mask & *pixel_byte)
        return 1;
    else
        return 0;
    }
