/*
 * w_gdi2d.c : 2D tile display routines (GDI version)
 *
 * Copyright (c) Yukihiko Aoki 1999
 * NetHack may be freely redistributed.  See license for details.
 *
 */

#include "hack.h"

#ifdef NH2K_EXTENDS

#include "win32api.h"
#include "w_main.h"
#include "w_dibtile.h"

/*
 * structure definitions
 */
typedef struct tagGDI2D{
    int        disp_mode;                   /* tile display mode                       */
    SIZE       disp_size;                   /* glyph tile size for display             */
    POINT      cursor;                      /* cursor position                         */
    TILELIST  *list;                        /* compressed bitmap list                  */
    TILELIST  *options;
    TILELIST  *effects;
    HDRAWDIB   hdd;                         /* DrawDib(Video for Windows) handle       */
    OFFSCREEN *buffer;                      /* DIB offscreen buffer for map            */
    OFFSCREEN *glyph_buf;                   /* DIB offscreen buffer for single tile    */
    short      fore_glyph[ROWNO][COLNO];    /* foreground glyph buffer                 */
    short      back_glyph[ROWNO][COLNO];    /* background glyph buffer                 */
    char       redraw_flg[ROWNO][COLNO];    /* redraw flag                             */
}GDI2D;

/*
 * local variables
 */
static GDI2D *canvas = NULL;

/*
 * external variables
 */
extern short glyph2tile[];

/*
 * local functions
 */
static void NDECL(GDI2D_uninit);
static void NDECL(GDI2D_clear);
static void FDECL(GDI2D_printGlyph, (int,int,int,int));
static void NDECL(GDI2D_updateGlyph);
static void FDECL(GDI2D_setCursor, (POINT));
static void FDECL(GDI2D_setDispMode, (int,int,int));
static void FDECL(GDI2D_draw, (HWND,HDC,BOOL));
static void FDECL(GDI2D_getRect, (RECT*));
static void FDECL(GDI2D_putGlyph, (HDC,RECT*,int));
static void NDECL(GDI2D_uninit);
static void FDECL(GDI2D_clip, (int,int));
static HPALETTE NDECL(GDI2D_getPalette);

/***************************************************************************************
 * initialize
 ***************************************************************************************/
BOOL GDI2D_init(WINCONTROL *ctl, void(*callbackProc)(int,int))
{
    canvas = NEWMEMORY(sizeof(GDI2D));
    if(!canvas) {
        return FALSE;
    }

    canvas->hdd = DrawDibOpen();

    canvas->list = Tile_load(
        NULL, ctl->glyph, ctl->cx, ctl->cy, NULL, callbackProc);
    canvas->options = Tile_load(
        NULL, ctl->option, ctl->cx, ctl->cy, NULL, NULL);
    canvas->effects = Tile_load(
        NULL, ctl->effect, ctl->cx, ctl->cy, NULL, NULL);
    if(!canvas->list) {
        GDI2D_uninit();
        return FALSE;
    }

    canvas->buffer = Offscreen_create(
        ctl->cx * COLNO,
        ctl->cy * ROWNO,
        canvas->list->hpal);
    if(!canvas->buffer) {
        GDI2D_uninit();
        return FALSE;
    }

    canvas->glyph_buf = Offscreen_create(
        ctl->cx,
        ctl->cy,
        canvas->list->hpal);
    if(!canvas->glyph_buf) {
        GDI2D_uninit();
        return FALSE;
    }

    canvas->disp_mode = D_DEFAULT;
    canvas->cursor.x  = -1;
    canvas->cursor.y  = -1;
    canvas->disp_size.cx = ctl->cx;
    canvas->disp_size.cy = ctl->cy;

    GDI2D_clear();

    return TRUE;
}

/***************************************************************************************
 * uninitialize
 ***************************************************************************************/
static void GDI2D_uninit()
{
    if(canvas) {
        if(canvas->list) {
            Tile_free(canvas->list);
        }
        if(canvas->buffer) {
            Offscreen_free(canvas->buffer);
        }
        if(canvas->glyph_buf) {
            Offscreen_free(canvas->glyph_buf);
        }
        if(canvas->hdd) {
            DrawDibClose(canvas->hdd);
        }
        DELMEMORY(canvas);
        canvas = NULL;
    }
}

/***************************************************************************************
 * set function pointers
 ***************************************************************************************/
void GDI2D_func(DISPPROC *procs)
{
    procs->uninit       = GDI2D_uninit;
    procs->clear        = GDI2D_clear;
    procs->printGlyph   = GDI2D_printGlyph;
    procs->updateGlyph  = GDI2D_updateGlyph;
    procs->setCursor    = GDI2D_setCursor;
    procs->setDispMode  = GDI2D_setDispMode;
    procs->draw         = GDI2D_draw;
    procs->getRect      = GDI2D_getRect;
    procs->getRect      = GDI2D_getRect;
    procs->putGlyph     = GDI2D_putGlyph;
    procs->getPalette   = GDI2D_getPalette;
    procs->setViewPoint = NULL;
    procs->dispEffect   = NULL;
    procs->clip         = GDI2D_clip;

}

/*-------------------------------------------------------------------------------------
 *
 *-------------------------------------------------------------------------------------*/
static void GDI2D_clip(int x, int y)
{
    if(canvas) {
/*
        canvas->clip.x = x;
        canvas->clip.y = y;
*/
    }
}


/*-------------------------------------------------------------------------------------
 * clear glyphs and offscreen buffer
 *-------------------------------------------------------------------------------------*/
void GDI2D_clear()
{
    int y, x;

    if(canvas) {
        for(y = 0; y < ROWNO; y++) {
            for(x = 0; x < COLNO; x++) {
                canvas->fore_glyph[y][x] = INVALID_GLYPH;
                canvas->back_glyph[y][x] = INVALID_GLYPH;
            }
        }
        Offscreen_clear(canvas->buffer);
    }
}

/*-------------------------------------------------------------------------------------
 *
 *-------------------------------------------------------------------------------------*/
void GDI2D_printGlyph(int col, int row, int back, int fore)
{
    /* not initialized */
    if(canvas) {
        /* clear offscreen */
        Offscreen_blackness(
            canvas->buffer,
            col * canvas->list->width,
            row * canvas->list->height,
            canvas->list->width,
            canvas->list->height);

        /* set new glyph */
        canvas->fore_glyph[row][col] = 
            (fore == cmap_to_glyph(S_stone)) ? INVALID_GLYPH : glyph2tile[fore];
        canvas->back_glyph[row][col] = 
            (back == cmap_to_glyph(S_stone)) ? INVALID_GLYPH : glyph2tile[back];

        /* set redraw flags */
        canvas->redraw_flg[row][col] = FLAG_ON;
    }
}

/*-------------------------------------------------------------------------------------
 * set glyph to destinated position
 *-------------------------------------------------------------------------------------*/
void GDI2D_updateGlyph()
{
    int x,y,ptx,pty,fgidx,bgidx;
    OFFSCREEN *ofb;

    if(!canvas) {
        return;
    }

    ofb = canvas->buffer;
    for(y = 0; y < ROWNO; y++) {
        for(x = 0; x < COLNO; x++) {
            ptx = x * canvas->list->width;
            pty = y * canvas->list->height;
            if(canvas->redraw_flg[y][x] == FLAG_OFF) {
                continue;
            }
            if(y >= 0 && x >= 0) {
                fgidx = canvas->fore_glyph[y][x];
                bgidx = canvas->back_glyph[y][x];
                if(bgidx != INVALID_GLYPH) {
                    Offscreen_blt(
                        ofb, ptx, pty,
                        canvas->list->width,
                        canvas->list->height,
                        &canvas->list->tiles[bgidx],0,0,BLTMODE_COPY);
                }
                if(fgidx != INVALID_GLYPH) {
                    Offscreen_blt(
                        ofb, ptx, pty,
                        canvas->list->width,
                        canvas->list->height,
                        &canvas->list->tiles[fgidx],0,0,BLTMODE_MASK);
                }
            }
            canvas->redraw_flg[y][x] = FLAG_OFF;
        }
    }
    if(canvas->cursor.x >= 0 && canvas->cursor.y >= 0) {
        Offscreen_blt(
            ofb,
            canvas->cursor.x * canvas->options->width,
            canvas->cursor.y * canvas->options->height,
            canvas->options->width,
            canvas->options->height,
            &canvas->options->tiles[0],0,0,BLTMODE_MASK);
    }
}

/*-------------------------------------------------------------------------------------
 * set cursor position
 *-------------------------------------------------------------------------------------*/
void GDI2D_setCursor(POINT cursor)
{
    if(canvas && (canvas->cursor.x != cursor.x || canvas->cursor.y != cursor.y)) {

        /* clear offscreen */
        if(canvas->cursor.x >= 0 && canvas->cursor.y >= 0) {
            Offscreen_blackness(
                canvas->buffer,
                canvas->cursor.x * canvas->list->width,
                canvas->cursor.y * canvas->list->height,
                canvas->list->width, canvas->list->height);
            canvas->redraw_flg[canvas->cursor.y][canvas->cursor.x] = FLAG_ON;
        }

        canvas->cursor = cursor;
        canvas->redraw_flg[canvas->cursor.y][canvas->cursor.x] = FLAG_ON;
    }
}

/*-------------------------------------------------------------------------------------
 * print offscreen buffer to window surface
 *-------------------------------------------------------------------------------------*/
void GDI2D_draw(HWND hwnd, HDC hdc, BOOL redraw_only)
{
    BOOL    is_samesize = FALSE;
    HDC     dcmem = NULL;
    HBITMAP  oldbmp;
    HPALETTE oldpal;
    RECT    rc;
    int     startx,starty;
    int     width, height;

    if(!canvas) {
        return;
    }

    /* check whether need stretch */
    if(canvas->disp_mode == D_DEFAULT) {
        is_samesize = TRUE;
    }

    GetClientRect(hwnd, &rc);
    oldpal = SelectPalette(hdc, canvas->list->hpal, FALSE);

    if(canvas->disp_mode == D_ENTIRE) {
        startx = 0;
        starty = 0;
        width = canvas->buffer->info->biWidth;
        height = canvas->buffer->info->biHeight;

    } else {
        width  = (rc.right - rc.left) * canvas->list->width / canvas->disp_size.cx;
        height = (rc.bottom - rc.top) * canvas->list->height / canvas->disp_size.cy;

        startx = (canvas->cursor.x * canvas->list->width) - width / 2;
        startx = (startx > (canvas->buffer->info->biWidth - width))
            ? (canvas->buffer->info->biWidth - width) : startx;
        startx = (startx < 0) ? 0 : startx;

        starty = (canvas->cursor.y * canvas->list->height) - height / 2;
        starty = (starty > (canvas->buffer->info->biHeight - height))
            ? (canvas->buffer->info->biHeight - height) : starty;
        starty = (starty < 0) ? 0 : starty;
    }

    /* now drawing offscreen bitmap to surface */
    if(is_samesize) {
        /* blt same size as bitmap, faster */
        dcmem = CreateCompatibleDC(hdc);
        oldbmp = SelectObject(dcmem, canvas->buffer->hbmp);
        BitBlt(hdc,
            rc.left,
            rc.top,
            rc.right - rc.left,
            rc.bottom - rc.top,
            dcmem,
            startx,
            starty,
            SRCCOPY);
        SelectObject(dcmem, oldbmp);
        DeleteDC(dcmem);

    } else if(!canvas->hdd) {
        /* if can't use DrawDib routine, use StretchBlt, but it will very slow */
        dcmem = CreateCompatibleDC(hdc);
        oldbmp = SelectObject(dcmem, canvas->buffer->hbmp);
        StretchBlt(hdc,
            rc.left,
            rc.top,
            rc.right - rc.left,
            rc.bottom - rc.top,
            dcmem,
            startx,
            starty,
            width,
            height,
            SRCCOPY);
        SelectObject(dcmem, oldbmp);
        DeleteDC(dcmem);

    } else {
        /* stretch blt by using DrawDib */
        DrawDibDraw(
            canvas->hdd,
            hdc,
            rc.left,
            rc.top,
            rc.right - rc.left,
            rc.bottom - rc.top,
            canvas->buffer->info,
            canvas->buffer->bits,
            startx,
            starty,
            width,
            height,
            0);
    }

    SelectPalette(hdc, oldpal, FALSE);
}

/*-------------------------------------------------------------------------------------
 * set display mode
 *-------------------------------------------------------------------------------------*/
static void GDI2D_setDispMode(int mode, int width, int height)
{
    if(canvas) {
        if(mode == D_DEFAULT) {
            canvas->disp_size.cx = canvas->list->width;
            canvas->disp_size.cy = canvas->list->height;
        }
        canvas->disp_mode = mode;
    }
}

/*-------------------------------------------------------------------------------------
 * calculate rectangle size best match for current display mode
 *-------------------------------------------------------------------------------------*/
static void GDI2D_getRect(RECT *rc)
{
    int width;
    int height;
    int max_width;
    int max_height;

    if(canvas && canvas->disp_mode != D_ENTIRE) {
        width  = rc->right - rc->left;
        height = rc->bottom - rc->top;
        max_width = canvas->disp_size.cx * COLNO;
        max_height = canvas->disp_size.cy * ROWNO;

        if(width > max_width) {
            rc->left += (width - max_width) / 2;
            rc->right = rc->left + max_width;
        }
        if(height > max_height) {
            rc->top += (height - max_height) / 2;
            rc->bottom = rc->top + max_height;
        }
    }
}

/*-------------------------------------------------------------------------------------
 * print single glyph to destineted rectangle
 *-------------------------------------------------------------------------------------*/
static void GDI2D_putGlyph(HDC hdc, RECT *rc, int glyph)
{
    int     idx;
    BOOL    is_samesize = FALSE;
    HDC     dcmem;
    HBITMAP oldbmp;

    if(canvas && canvas->glyph_buf) {
        idx = glyph2tile[glyph];

        /* check whether blt can be using BitBlt */
        if((rc->right - rc->left) == canvas->list->width
            && (rc->bottom - rc->top) == canvas->list->height) {
            is_samesize = TRUE;
        }

        /* draw glyph to offscreen buffer */
        Offscreen_clear(canvas->glyph_buf);
        Offscreen_blt(
            canvas->glyph_buf,
            0,
            0,
            canvas->list->width,
            canvas->list->height,
            &canvas->list->tiles[idx],0,0,BLTMODE_MASK);

        /* now drawing offscreen bitmap to surface */
        if(is_samesize) {
            /* blt same size as bitmap, faster */
            dcmem = CreateCompatibleDC(hdc);
            oldbmp = SelectObject(dcmem, canvas->glyph_buf->hbmp);
            BitBlt(hdc,
                rc->left,
                rc->top,
                rc->right - rc->left,
                rc->bottom - rc->top,
                dcmem,
                0,
                0,
                SRCCOPY);
            SelectObject(dcmem, oldbmp);
            DeleteDC(dcmem);

        } else if(!canvas->hdd) {
            /* if can't use DrawDib routine, use StretchBlt, but it will very slow */
            dcmem = CreateCompatibleDC(hdc);
            oldbmp = SelectObject(dcmem, canvas->glyph_buf->hbmp);
            StretchBlt(hdc,
                rc->left,
                rc->top,
                rc->right - rc->left,
                rc->bottom - rc->top,
                dcmem,
                0, 0,
                canvas->glyph_buf->info->biWidth,
                canvas->glyph_buf->info->biHeight,
                SRCCOPY);
            SelectObject(dcmem, oldbmp);
            DeleteDC(dcmem);

        } else {
            /* stretch blt by using DrawDib */
            DrawDibDraw(
                canvas->hdd,
                hdc,
                rc->left,
                rc->top,
                rc->right - rc->left,
                rc->bottom - rc->top,
                canvas->glyph_buf->info,
                canvas->glyph_buf->bits,
                0, 0,
                canvas->glyph_buf->info->biWidth,
                canvas->glyph_buf->info->biHeight,
                0);
        }
    }
}

/*-------------------------------------------------------------------------------------
 * return palette currently using
 *-------------------------------------------------------------------------------------*/
static HPALETTE GDI2D_getPalette(void)
{
    if(canvas) {
        return canvas->list->hpal;
    }

    return NULL;
}

#endif /* NH2K_EXTENDS */
