/*
 * w_gdi2d.c : 2D tile display routines (GDI version)
 *
 * Copyright (c) Yukihiko Aoki 1999, 2005
 * 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"
#include "w_effect.h"

#define PETMASK     0x8000

/*
 * structure definitions
 */
typedef struct tagGDI2D{
    int        disp_mode;                   /* tile display mode                       */
    SIZE       disp_size;                   /* glyph tile size for display             */
    BOOL       view_flg;                    /* */
    POINT      view_point;                  /**/
    POINT      cursor;                      /* cursor position                         */
    POINT      start;
    TILELIST  *list;                        /* compressed bitmap list                  */
    TILELIST  *options;
    TILELIST  *effect_tile;
    HDRAWDIB   hdd;                         /* DrawDib(Video for Windows) handle       */
    OFFSCREEN *buffer;                      /* DIB offscreen buffer for map            */
    OFFSCREEN *glyph_buf;                   /* DIB offscreen buffer for single tile    */
#ifdef DISPLAY_EFFECT
    EFFECTITEM effect_item[EFFECT_MAX];     /* effect informations */
#endif /* DISPLAY_EFFECT */
    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));
#ifdef DISPLAY_EFFECT
static void FDECL(GDI2D_dispEffect, (HWND,HDC,int,int,int,int));
#endif /* DISPLAY_EFFECT */
static void FDECL(GDI2D_setViewPoint, (int,int,int));
static HPALETTE NDECL(GDI2D_getPalette);

/***************************************************************************************
 * initialize
 ***************************************************************************************/
BOOL GDI2D_init(WINCONTROL *ctl, void(*callbackProc)(int,int))
{
    RECT rcValid;

    canvas = calloc(1, sizeof(GDI2D));
    if (!canvas) {
        return FALSE;
    }

    canvas->hdd = DrawDibOpen();
    str2rect(ctl->valid_rect, &rcValid);

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

    canvas->options = Tile_load(
        NULL, ctl->option, ctl->cx, ctl->cy, NULL, NULL);
    if (!canvas->options || canvas->options->numtiles != 2) {
        GDI2D_uninit();
        return FALSE;
    }

#ifdef DISPLAY_EFFECT
    canvas->effect_tile = Tile_load(
        NULL, ctl->effect, ctl->cx, ctl->cy, NULL, NULL);
    if (canvas->effect_tile) {
        str2effects(ctl->animation, canvas->effect_item);
    }
#endif /* DISPLAY_EFFECT */

    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->options) {
            Tile_free(canvas->options);
        }
        if (canvas->effect_tile) {
            Tile_free(canvas->effect_tile);
        }
        if (canvas->buffer) {
            Offscreen_free(canvas->buffer);
        }
        if (canvas->glyph_buf) {
            Offscreen_free(canvas->glyph_buf);
        }
        if (canvas->hdd) {
            DrawDibClose(canvas->hdd);
        }
        free(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 = GDI2D_setViewPoint;
#ifdef DISPLAY_EFFECT
    procs->dispEffect   = GDI2D_dispEffect;
#endif /* DISPLAY_EFFECT */
    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];
        if (glyph_is_pet(fore)) {
            canvas->fore_glyph[row][col] |= PETMASK;
        }
        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) {
                    if(fgidx & PETMASK) {
                        fgidx &= (0xFFFF^PETMASK);
                    }
                    Offscreen_blt(
                        ofb, ptx, pty,
                        canvas->list->width,
                        canvas->list->height,
                        &canvas->list->tiles[fgidx],0,0,BLTMODE_MASK);
                    if (iflags.hilite_pet && canvas->fore_glyph[y][x] & PETMASK) {
                        Offscreen_blt(
                            ofb, ptx, pty, 
                            canvas->options->width,
                            canvas->options->height,
                            &canvas->options->tiles[1],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     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) {
        canvas->start.x = 0;
        canvas->start.y = 0;
        width = canvas->buffer->info->biWidth;
        height = canvas->buffer->info->biHeight;

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

        canvas->start.x = canvas->buffer->info->biWidth * canvas->view_point.x / 100 - width / 2;
        canvas->start.x = (canvas->start.x > (canvas->buffer->info->biWidth - width))
            ? (canvas->buffer->info->biWidth - width) : canvas->start.x;
        canvas->start.x = (canvas->start.x < 0) ? 0 : canvas->start.x;

        canvas->start.y = canvas->buffer->info->biHeight * canvas->view_point.y / 100 - height / 2;
        canvas->start.y = (canvas->start.y > (canvas->buffer->info->biHeight - height))
            ? (canvas->buffer->info->biHeight - height) : canvas->start.y;
        canvas->start.y = (canvas->start.y < 0) ? 0 : canvas->start.y;

    } 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;

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

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

    /* 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,
            canvas->start.x,
            canvas->start.y,
            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,
            canvas->start.x,
            canvas->start.y,
            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,
            canvas->start.x,
            canvas->start.y,
            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);
        }
    }
}

/*-------------------------------------------------------------------------------------
 * 
 *-------------------------------------------------------------------------------------*/
static void GDI2D_setViewPoint(int flag, int x, int y)
{
    if (flag == VIEWPOINT_SET) {
        canvas->view_flg = TRUE;
        canvas->view_point.x = x;
        canvas->view_point.y = y;
    } else {
        canvas->view_flg = FALSE;
    }
}

#ifdef DISPLAY_EFFECT
/*-------------------------------------------------------------------------------------
 * Special effects
 *-------------------------------------------------------------------------------------*/
static void GDI2D_dispEffect(HWND hwnd, HDC hdc, int col, int row, int type, int delay)
{
    int i, idx;
/*    COLORREF c;*/
    HDC dcMem;
    HBITMAP oldBmp;
    HPALETTE oldPal;
    int x, y, dx, dy;
    
    if (!canvas) {
        return;
    }

    x = col * canvas->list->width;
    y = row * canvas->list->height;
    dx = x - canvas->start.x;
    dy = y - canvas->start.y;

    idx = canvas->effect_item[type].start;
    oldPal = SelectPalette(hdc, canvas->list->hpal, FALSE);
    for (i = idx; i < (idx + canvas->effect_item[type].count); i++) {
        Offscreen_copy(canvas->glyph_buf,
            0, 0, canvas->effect_tile->width,canvas->effect_tile->height,
            canvas->buffer,
            x, y);
        Offscreen_blt(canvas->glyph_buf,
            0, 0, canvas->effect_tile->width,canvas->effect_tile->height,
            &canvas->effect_tile->tiles[i], 0, 0, BLTMODE_MASK);
        dcMem = CreateCompatibleDC(hdc);
        oldBmp = SelectObject(dcMem, canvas->glyph_buf->hbmp);
        BitBlt(hdc, dx, dy, canvas->effect_tile->width, canvas->effect_tile->height,
            dcMem, 0, 0, SRCCOPY);
        SelectObject(dcMem, oldBmp);
        DeleteDC(dcMem);
        Sleep(delay);
/*
        DrawDibDraw(
            canvas->hdd,
            hdc, dx, dy, canvas->effect_tile->width, canvas->effect_tile->height,
            canvas->glyph_buf->info, canvas->glyph_buf->bits,
            0, 0, canvas->effect_tile->width, canvas->effect_tile->height, 0);
*/
    }
    SelectPalette(hdc, oldPal, FALSE);

    /* Restore window */
    GDI2D_draw(hwnd, hdc, FALSE);
}
#endif /* DISPLAY_EFFECT */


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

    return NULL;
}

#endif /* NH2K_EXTENDS */
