/**
 ** setmode.c ---- video mode setup
 **
 ** Copyright (c) 1995 Csaba Biegl, 820 Stirrup Dr, Nashville, TN 37221
 ** [e-mail: csaba@vuse.vanderbilt.edu]
 ** Hartmut Schirmer (hsc@techfak.uni-kiel.de)
 ** Andrzej Lawa [FidoNet: Andrzej Lawa 2:480/19.77]
 **
 ** This file is part of the GRX graphics library.
 **
 ** The GRX graphics library is free software; you can redistribute it
 ** and/or modify it under some conditions; see the "copying.grx" file
 ** for details.
 **
 ** This library is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 **
 **/

#include <ctype.h>
#include <stdarg.h>

#include "libgrx.h"
#include "arith.h"
#include "memmode.h"
#include "grdriver.h"

GrVideoMode * _gr_selectmode(GrVideoDriver *drv,int w,int h,int bpp,
                             int txt,unsigned int *ep)
{
    #define ERROR(des,act)   ((des > act) ? ((des - act) + 20000) : (act - des))
    int  n;
    unsigned int cerr, serr, err[2];
    GrVideoMode *best, *mp;
    GRX_ENTER();
    best = NULL;
    mp = drv->modes;
    for (n = drv->nmodes; --n >= 0; mp++) {
        if (!mp->present) continue;
        if (!mp->extinfo) continue;
        if ((mp->extinfo->mode != GR_frameText) ? txt : !txt) continue;
        cerr = ERROR(bpp, mp->bpp);
        serr = ERROR(w, mp->width) + ERROR(h, mp->height);
        if (((ep) ? FALSE : ((ep = err),TRUE)) ||
            ((cerr <  ep[0])) ||
            ((cerr == ep[0]) && (serr < ep[1]))) {
            best  = mp;
            if (!cerr && !serr) break;
            ep[0] = cerr;
            ep[1] = serr;
        }
    }
    if(drv->inherit) {
        mp = (drv->inherit->selectmode) (drv->inherit, w, h, bpp, txt, ep);
        if(mp) best = mp;
    }
    GRX_RETURN(best);
}

static int buildframedriver(GrVideoMode *mp,GrFrameDriver *drv)
{
    GrFrameDriver *d1, *d2;
    int res = TRUE;
    GRX_ENTER();
    res = TRUE;
    d1 = _GrFindFrameDriver(mp->extinfo->mode);
    d2 = mp->extinfo->drv;
    if (d1) sttcopy(drv, d1);
    if (d2) {
        int compl = TRUE;
        #define MERGE(F) if(d2->F) drv->F = d2->F; else compl = FALSE;
        MERGE(readpixel);
        MERGE(drawpixel);
        MERGE(drawhline);
        MERGE(drawvline);
        MERGE(drawblock);
        MERGE(drawline);
        MERGE(drawbitmap);
        MERGE(drawpattern);
        MERGE(bitblt);
        MERGE(bltv2r);
        MERGE(bltr2v);
        MERGE(getindexedscanline);
        MERGE(putscanline);
        if (compl) {
            memcpy(drv, d2, offsetof(GrFrameDriver, readpixel));
            goto done; /* TRUE */
        }
        if (!d1) { res = FALSE; goto done; }
        if ((d2->mode == d1->mode) &&
            (d2->rmode == d1->rmode) &&
            (d1->is_video ? d2->is_video : !d2->is_video) &&
            (d2->row_align <= d1->row_align) && !(d1->row_align % d2->row_align) &&
            (d2->num_planes == d1->num_planes) &&
            (d2->max_plane_size >= d1->max_plane_size) &&
            (d2->bits_per_pixel == d1->bits_per_pixel)) {
            drv->init = d2->init ? d2->init : d1->init;
            goto done; /* TRUE */
        }
    }
    if (!d1) { res = FALSE; goto done; }
    sttcopy(drv, d1);
done:
    GRX_RETURN(res);
}

static int buildcontext(GrVideoMode *mp,GrFrameDriver *fdp,GrContext *cxt)
{
    long plsize;
    int res;
    GRX_ENTER();
    res = FALSE;
    plsize = umul32(mp->lineoffset, mp->height);
    DBGPRINTF(DBG_SETMD,("buildcontext - Mode Frame buffer = 0x%x\n",mp->extinfo->frame));
    DBGPRINTF(DBG_SETMD,("buildcontext - Mode Frame selector = 0x%x\n",mp->extinfo->LFB_Selector));
    sttzero(cxt);
    #if !(defined(__XWIN__) && !defined(XF86DGA_FRAMEBUFFER))
    if (mp->extinfo->flags&GR_VMODEF_LINEAR) {
        DBGPRINTF(DBG_SETMD,("buildcontext - Linear Mode\n"));
        cxt->gc_baseaddr[0] =
        cxt->gc_baseaddr[1] =
        cxt->gc_baseaddr[2] =
        cxt->gc_baseaddr[3] = LINP_PTR(mp->extinfo->frame);
        cxt->gc_selector    = mp->extinfo->LFB_Selector;
    } else
    #endif
    if (mp->extinfo->flags&GR_VMODEF_MEMORY) {
        DBGPRINTF(DBG_SETMD,("buildcontext - Memory Mode\n"));
        if (plsize > fdp->max_plane_size) goto done; /* FALSE */
        if (mp->lineoffset % fdp->row_align) goto done; /* FALSE */
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->present    = %d\n",mp->present));
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->bpp        = %d\n",mp->bpp));
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->width      = %d\n",mp->width));
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->height     = %d\n",mp->height));
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->mode       = %d\n",mp->mode));
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->lineoffset = %d\n",mp->lineoffset));
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->ext->mode  = %d\n",mp->extinfo->mode));
        DBGPRINTF(DBG_SETMD,("buildcontext - mp->ext->flags = %x\n",mp->extinfo->flags));
        if (mp->bpp==4) {
            int m_incr = mp->lineoffset*mp->height;
            cxt->gc_baseaddr[0] = mp->extinfo->frame;
            cxt->gc_baseaddr[1] = cxt->gc_baseaddr[0] + m_incr;
            cxt->gc_baseaddr[2] = cxt->gc_baseaddr[1] + m_incr;
            cxt->gc_baseaddr[3] = cxt->gc_baseaddr[2] + m_incr;
        } else {
            cxt->gc_baseaddr[0] =
            cxt->gc_baseaddr[1] =
            cxt->gc_baseaddr[2] =
            cxt->gc_baseaddr[3] = mp->extinfo->frame;
        }
    } else {
        if (plsize > fdp->max_plane_size) goto done; /* FALSE */
        if (!mp->extinfo->setbank && (plsize > 0x10000L)) goto done; /* FALSE */
        if (mp->lineoffset % fdp->row_align) goto done; /* FALSE */
        cxt->gc_baseaddr[0] =
        cxt->gc_baseaddr[1] =
        cxt->gc_baseaddr[2] =
        cxt->gc_baseaddr[3] = LINP_PTR(mp->extinfo->frame);
        cxt->gc_selector    = LINP_SEL(mp->extinfo->frame);
    }

    cxt->gc_onscreen    = !(mp->extinfo->flags&GR_VMODEF_MEMORY);
    /* Why do we default to screen driver ?? */
    cxt->gc_onscreen    = TRUE;
    cxt->gc_lineoffset  = mp->lineoffset;
    cxt->gc_xcliphi     = cxt->gc_xmax = mp->width  - 1;
    cxt->gc_ycliphi     = cxt->gc_ymax = mp->height - 1;
    cxt->gc_driver      = &DRVINFO->sdriver;
        
    res = TRUE;
        
    DBGPRINTF(DBG_SETMD,("buildcontext - context buffer 0 = 0x%x\n",cxt->gc_baseaddr[0]));
    DBGPRINTF(DBG_SETMD,("buildcontext - context buffer 1 = 0x%x\n",cxt->gc_baseaddr[1]));
    DBGPRINTF(DBG_SETMD,("buildcontext - context buffer 2 = 0x%x\n",cxt->gc_baseaddr[2]));
    DBGPRINTF(DBG_SETMD,("buildcontext - context buffer 3 = 0x%x\n",cxt->gc_baseaddr[3]));
done:
    GRX_RETURN(res);
}

static int errhdlr(char *msg)
{
    if (DRVINFO->errsfatal) {
        DRVINFO->moderestore = TRUE;
        _GrCloseVideoDriver();
        fprintf(stderr, "GrSetMode: %s\n", msg);
        exit(1);
    }
    return(FALSE);
}

int GrSetGraphicsMode(int width, int height, int bpp, int noclear)
{
    GrGraphicsMode gm = GR_width_height_bpp_graphics;

    if (noclear) gm = GR_NC_width_height_bpp_graphics;

    return GrSetMode(gm, width, height, bpp);
}

int GrUnsetGraphicsMode(void)
{
    return GrSetMode(GR_default_text);
}

int GrSetMode(GrGraphicsMode which,...)
{
    int  w,h,pl,vw,vh;
    int  t,noclear,res;
    long c;
    va_list ap;
    GRX_ENTER();
    pl = 0;
    vw = 0;
    vh = 0;
    t = FALSE;
    noclear = FALSE;
    c = 0;
    res = FALSE;
    
    DBGPRINTF(DBG_SETMD,("Mode: %d\n",(int)which));

    _GrIniUserEncoding();
    
    if (DRVINFO->vdriver == NULL) {
        GrSetDriver(NULL);
        if (DRVINFO->vdriver == NULL) {
            res = errhdlr("could not find suitable video driver");
            goto done;
        }
    }

    va_start(ap,which);
    switch(which) {
        case GR_NC_default_text:
            noclear = TRUE;
        case GR_default_text:
            w = DRVINFO->deftw;
            h = DRVINFO->defth;
            c = DRVINFO->deftc;
            t = TRUE;
            break;
        case GR_NC_320_200_graphics:
            noclear = TRUE;
        case GR_320_200_graphics:
            w = 320;
            h = 200;
            c = DRVINFO->defgc;
            break;
        case GR_NC_default_graphics:
            noclear = TRUE;
        case GR_default_graphics:
            w = DRVINFO->defgw;
            h = DRVINFO->defgh;
            c = DRVINFO->defgc;
            break;
        case GR_NC_width_height_graphics:
            noclear = TRUE;
        case GR_width_height_graphics:
            w = va_arg(ap,int);
            h = va_arg(ap,int);
            c = DRVINFO->defgc;
            break;
        case GR_NC_biggest_graphics:
            noclear = TRUE;
        case GR_biggest_graphics:
            w = DRVINFO->biggw;
            h = DRVINFO->biggh;
            pl = 32;
            break;
        case GR_NC_width_height_color_graphics:
            noclear = TRUE;
        case GR_width_height_color_graphics:
            w = va_arg(ap,int);
            h = va_arg(ap,int);
            c = va_arg(ap,long);
            break;
        case GR_NC_width_height_bpp_graphics:
            noclear = TRUE;
        case GR_width_height_bpp_graphics:
            w  = va_arg(ap,int);
            h  = va_arg(ap,int);
            pl = va_arg(ap,int);
            break;
        case GR_NC_custom_graphics:
            noclear = TRUE;
        case GR_custom_graphics:
            w  = va_arg(ap,int);
            h  = va_arg(ap,int);
            c  = va_arg(ap,long);
            vw = va_arg(ap,int);
            vh = va_arg(ap,int);
            break;
        case GR_NC_custom_bpp_graphics:
            noclear = TRUE;
        case GR_custom_bpp_graphics:
            w  = va_arg(ap,int);
            h  = va_arg(ap,int);
            pl = va_arg(ap,int);
            vw = va_arg(ap,int);
            vh = va_arg(ap,int);
            break;
        default:
            va_end(ap);
            res = errhdlr("unknown video mode");
            goto done;
    }
    va_end(ap);

    if (c) for (pl = 1; (pl < 32) && ((1UL << pl) < c); pl++);

    for ( ; ; ) {
        GrContext     cxt;
        GrFrameDriver fdr;
        GrVideoMode  *mdp, vmd;
        mdp = (DRVINFO->vdriver->selectmode)(DRVINFO->vdriver, w, h, pl, t, NULL);
        if(!mdp) {
            res = errhdlr("could not find suitable video mode");
            goto done;
        }
        sttcopy(&vmd,mdp);
        if ((t || buildframedriver(&vmd, &fdr)) &&
            (*vmd.extinfo->setup)(&vmd, noclear) &&
            (t || buildcontext(&vmd, &fdr, &cxt))) {
            if ((!t) &&
                ((vw > vmd.width) || (vh > vmd.height)) &&
                (vmd.extinfo->setvsize != NULL) &&
                (vmd.extinfo->scroll   != NULL)) {
                int ww = vmd.width  = imax(vw, vmd.width);
                int hh = vmd.height = imax(vh, vmd.height);
                if (!(*vmd.extinfo->setvsize)(&vmd, ww, hh, &vmd) ||
                    !buildcontext(&vmd, &fdr, &cxt)) {
                    sttcopy(&vmd, mdp);
                    buildcontext(&vmd, &fdr, &cxt);
                    (*vmd.extinfo->setup)(&vmd, noclear);
                }
            }
            DRVINFO->setbank    = (void (*)(int    ))_GrDummyFunction;
            DRVINFO->setrwbanks = (void (*)(int,int))_GrDummyFunction;
            DRVINFO->curbank    = (-1);
            DRVINFO->splitbanks = FALSE;
            if (!t) {
                if (vmd.extinfo->setbank) {
                    DRVINFO->setbank = vmd.extinfo->setbank;
                }
                if (vmd.extinfo->setrwbanks) {
                    DRVINFO->setrwbanks = vmd.extinfo->setrwbanks;
                    DRVINFO->splitbanks = TRUE;
                }
                if (umul32(vmd.lineoffset, vmd.height) <= 0x10000L) {
                    DRVINFO->splitbanks = TRUE;
                }
            } else {
                sttzero(&cxt);
                sttcopy(&fdr, &DRVINFO->tdriver);
                cxt.gc_driver = &DRVINFO->tdriver;
            }
            sttcopy(&CXTINFO->current, &cxt);
            sttcopy(&CXTINFO->screen,  &cxt);
            sttcopy(&DRVINFO->fdriver, &fdr);
            sttcopy(&DRVINFO->sdriver, &fdr);
            sttcopy(&DRVINFO->actmode, &vmd);
            DRVINFO->curmode = mdp;
            DRVINFO->mcode   = which;
            DRVINFO->vposx   = 0;
            DRVINFO->vposy   = 0;
            DBGPRINTF(DBG_SETMD,("GrResetColors ...\n"));
            if (!_GrResetColors()) {
                res = errhdlr("could not set color mode");
                goto done;
            }
            DBGPRINTF(DBG_SETMD,("GrResetColors done\n"));
            if (fdr.init) {
                DBGPRINTF(DBG_SETMD,("fdr.init ...\n"));
                (*fdr.init)(&DRVINFO->actmode);
                DBGPRINTF(DBG_SETMD,("fdr.init done\n"));
            }
            if (DRVINFO->mdsethook) {
                DBGPRINTF(DBG_SETMD,("mdsethook ...\n"));
                (*DRVINFO->mdsethook)();
                DBGPRINTF(DBG_SETMD,("mdsethook done\n"));
            }
            DBGPRINTF(DBG_SETMD,("GrSetMode complete\n"));
            res = TRUE;
            goto done;
        }
        mdp->present = FALSE;
    }
done:
    GRX_RETURN(res);
}

