/*
 * w_dibtile.c : Windows DIB(Device Independent Bitmap) tile related routines
 *
 * 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"

/*
 * Local functions
 */
static BOOL     FDECL(_checkTileFormat, (HBITMAP,int,int));
static HPALETTE FDECL(_createPalette, (HBITMAP));

/***************************************************************************************
 * Load tile bitmaps and build list
 ***************************************************************************************/
TILELIST *Tile_load(
    HINSTANCE      hinst,               /* module instance handle */
    char          *filename,            /* bitmap file name */
    BYTE           width,               /* single tile width */
    BYTE           height,              /* single tile height */
    RECT          *defrc,               /* default valid rectangle size */
    void (*callbackProc)(int, int)      /* callback function to report progress */
)
{
    TBIT       *tmpbuf = NULL;
    TBIT      **ptr   = NULL;
    TILELIST   *list  = NULL;
    BYTE       *pbase = NULL;
    BYTE       *pbits = NULL;
    HBITMAP     hbmp  = NULL;
    HPALETTE    hpal  = NULL;
    TILEDATA   *ptile = NULL;
    DIBSECTION  ds;
    int         i, j;
    int         xcount, ycount;
    int         x, y, cx, cy;
    RECT        rc;
    long        offset;
#ifdef COMPRESS_TILE
    BYTE       *prev;
    WORD        numcolor;
    BYTE        len;
    BYTE        col;
#endif

    /* Load bitmap as DIB section */
    hbmp = LoadImage(hinst, filename,
        IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION|(hinst ? 0 : LR_LOADFROMFILE));
    if (!_checkTileFormat(hbmp, width, height)) {
        /* Failed to load bitmap */
        goto cleanup;
    }

    /* Get tile metrics */
    GetObject(hbmp, sizeof(DIBSECTION), &ds);
    xcount = ds.dsBmih.biWidth / width;
    ycount = ds.dsBmih.biHeight / height;

    /* Set progress range */
    if (callbackProc) {
        callbackProc(PROGRESS_SETRANGE, xcount * ycount);
    }

    /* Create palette */
    hpal = CreateBitmapPalette(hbmp);
    if (!hpal) {
        /* Can't create palette */
        goto cleanup;
    }

    /* Create TILELIST */
    list = (TILELIST*)calloc(1, sizeof(TILELIST));
    if (list) {
        list->width = width;
        list->height = height;
        list->xcount = xcount;
        list->hpal = hpal;
        if(defrc != NULL) {
            CopyRect(&list->defrc, defrc);
        }
    } else {
        DeleteObject(hpal);
        goto cleanup;
    }

    /* Create temporary buffer */
    tmpbuf = (TBIT *)calloc(width, sizeof(TBIT));

    /* Allocate TILE array*/
    list->tiles = (TILEDATA *)calloc(xcount * ycount, sizeof(TILEDATA));
    if (!list->tiles) {
        /* Memory allocation error */
        Tile_free(list);
        list = NULL;
        goto cleanup;
    }
    list->numtiles = xcount * ycount;

    offset = ds.dsBm.bmWidthBytes % 4;
    /* Be careful! default DIB is bottom up */
    for (y = 0; y < ycount; y++) {
        for (x = 0; x < xcount; x++) {

            /* Update progress */
            if (callbackProc) {
                callbackProc(PROGRESS_SETPOS, x + (y * xcount));
            }

            ptile = &(list->tiles[x + (y * xcount)]);
            ptr = (TBIT **)calloc(height, sizeof(TBIT*));
            ptile->data = ptr;
            ptile->cx = width;
            ptile->cy = height;
            ptile->size = 0;
            rc.top = rc.left = rc.bottom = rc.right = -1;

            pbase = pbits = (BYTE *)ds.dsBm.bmBits
                          + (x * width)
                          + ((ycount - y) * height * (ds.dsBm.bmWidthBytes + offset))
                          - ds.dsBm.bmWidthBytes - offset;
            i = 0;
            for (cy = 0; cy < height; cy++) {
#ifdef COMPRESS_TILE
                numcolor = 0;
                len = 0;
                col = 0;
                prev = NULL;
                for (cx = 0; cx < width; cx++) {
                    if (*pbits != COLOR_TRANSPARENT) {
                        if (rc.top == -1) {
                            rc.top = cy;
                        }
                        rc.bottom = cy;
                        if (rc.left == -1) {
                            rc.left = cx;
                        }
                        rc.left = min(rc.left, cx);
                        rc.right = max(rc.right, cx);
                    }
                    if (!prev) {
                        col = *pbits;
                        len++;
                    } else if(*prev == *pbits) {
                        len++;
                    } else {
                        tmpbuf[numcolor] = MAKEWORD(col,len);
                        numcolor++;
                        col = *pbits;
                        len = 1;
                    }
                    prev = pbits;
                    pbits++;
                }
                tmpbuf[numcolor] = MAKEWORD(col,len);
                numcolor++;

                *ptr = (TBIT*)calloc(numcolor + 1, sizeof(TBIT));
                for (j = 1; j <= numcolor; j++) {
                    (*ptr)[j] = tmpbuf[j - 1];
                }
                (*ptr)[0] = numcolor;
#else /* COMPRESS_TILE */
                for (cx = 0; cx < width; cx++) {
                    if (*pbits != COLOR_TRANSPARENT) {
                        if (rc.top == -1) {
                            rc.top = cy;
                        }
                        rc.bottom = cy;
                        if (rc.left == -1) {
                            rc.left = cx;
                        }
                        rc.left = min(rc.left, cx);
                        rc.right = max(rc.right, cx);
                    }
                    tmpbuf[cx] = *(pbits++);
                }
                *ptr = (TBIT*)calloc(width, sizeof(TBIT));
                for (j = 0; j < width; j++) {
                    (*ptr)[j] = tmpbuf[j];
                }
#endif
                pbase -= (ds.dsBm.bmWidthBytes + offset);
                pbits = pbase;
                ptr++;
            }
            if (defrc 
                && (rc.left < defrc->left || rc.top < defrc->top 
                || rc.right > defrc->right || rc.bottom > defrc->bottom)) {
                ptile->size = 1;
            }
        }
    }

cleanup:

    if (hbmp) {
        /* bitmap resource is not needed */
        DeleteObject(hbmp);
    }
    if (tmpbuf) {
        free(tmpbuf);
    }

    return list;
}

/***************************************************************************************
 * Free tile list
 ***************************************************************************************/
void Tile_free(TILELIST *list)
{
    int i, n;

    if (list) {
        if (list->hpal) {
            DeleteObject(list->hpal);
        }
        if (list->tiles) {
            for (i = 0; i < list->numtiles; i++) {
                TBIT** ptr = list->tiles[i].data;
                for (n = 0; n < list->height; n++) {
                    free(*ptr);
                    ptr++;
                }
                free(list->tiles[i].data);
            }
            free(list->tiles);
        }
        free(list);
    }
}

/***************************************************************************************
 * Copy tile to offscreen
 ***************************************************************************************/
void Offscreen_blt(
    OFFSCREEN *buf,
    int dx,
    int dy,
    int cx,
    int cy,
    TILEDATA *tile,
    int sx,
    int sy,
    DWORD mode)
{
    BYTE *dest_base, *dest;
    long offset;
    int x, y;
#ifdef COMPRESS_TILE
    int idx, xx;
    BYTE length, color;
#endif /* COMPRESS_TILE */

    offset = buf->info->biWidth % 4;
    dest_base = buf->bits + dx + 
        ((buf->info->biHeight - dy) * (buf->info->biWidth + offset))
        - buf->info->biWidth - offset;

    for (y = sy; y < (sy + cy); y++) {
        dest = dest_base;
#ifdef COMPRESS_TILE
        for (x = 0, idx = 1; x + HIBYTE(tile->data[y][idx]) < sx; idx++) {
            x += HIBYTE(tile->data[y][idx]);
        }
        length = x + HIBYTE(tile->data[y][idx]) - sx;
        for (x = 0; x < cx && idx <= tile->data[y][0];length = HIBYTE(tile->data[y][++idx])) {
            color = LOBYTE(tile->data[y][idx]);
            length = min(length, cx - x);
            if (mode == BLTMODE_COPY || color != COLOR_TRANSPARENT) {
                if (mode == BLTMODE_MESH) {
                    for (xx = 0; xx < length; xx++) {
                        if (((sx + x + xx) % 2) == (y % 2)) {
                            *(dest + xx) = color;
                        }
                    }
                }else {
                    FillMemory(dest, length, color);
                }
            }
            x += length;
            dest += length;
        }
#else /* COMPRESS_TILE */
        for (x = sx; x < (sx + cx); x++) {
            if (tile->data[y][x] != COLOR_TRANSPARENT) {
                *dest = tile->data[y][x];
            }
            dest++;
        }
#endif /* COMPRESS_TILE */
        dest_base -= (buf->info->biWidth + offset);
    }
}

/***************************************************************************************
 * copy bits
 ***************************************************************************************/
void Offscreen_copy(
    OFFSCREEN *dst,
    int dx,
    int dy,
    int cx,
    int cy,
    OFFSCREEN *src,
    int sx,
    int sy)
{
    BYTE *dst_base;
    BYTE *src_base;
    int y;

    dst_base = dst->bits + dx + ((dst->info->biHeight - (dy + cy)) * dst->info->biWidth);
    src_base = src->bits + sx + ((src->info->biHeight - (sy + cy)) * src->info->biWidth);

    for (y = 0; y < cy; y++) {
        CopyMemory(dst_base, src_base, cx);
        dst_base += dst->info->biWidth;
        src_base += src->info->biWidth;
    }
}

/***************************************************************************************
 * create offscreen
 ***************************************************************************************/
OFFSCREEN *Offscreen_create(int cx, int cy, HPALETTE hpal)
{
    PALETTEENTRY      entry[COLOR_PALSIZE];
    OFFSCREEN        *buf = NULL;
    BITMAPINFOHEADER *info = NULL;
    RGBQUAD          *rgb;
    UINT              numcolor;
    UINT              i;
    HDC               hdc;

    info = (BITMAPINFOHEADER *)calloc(1, sizeof(BITMAPINFO)
      + (sizeof(RGBQUAD) * COLOR_PALSIZE));
    if (!info) {
        return NULL;
    }

    info->biSize          = sizeof(BITMAPINFOHEADER);
    info->biWidth         = cx;
    info->biHeight        = cy;
    info->biPlanes        = 1;
    info->biBitCount      = COLOR_BITCOUNT;
    info->biCompression   = BI_RGB;

    rgb = (RGBQUAD *)(info + 1);
    numcolor = GetPaletteEntries(hpal, 0, COLOR_PALSIZE, entry);
    if (numcolor <= 0) {
        free(info);
        return NULL;
    }
    for (i = 0; i < numcolor; i++) {
        rgb->rgbBlue  = entry[i].peBlue;
        rgb->rgbGreen = entry[i].peGreen;
        rgb->rgbRed   = entry[i].peRed;
        rgb->rgbReserved = 0;
        rgb++;
    }

    buf = (OFFSCREEN *)calloc(1, sizeof(OFFSCREEN));
    if (!buf) {
        free(info);
        return NULL;
    }

    hdc = GetDC(NULL);
    buf->hbmp = CreateDIBSection(
        hdc,
        (BITMAPINFO *)info,
        DIB_RGB_COLORS,
        &(buf->bits),
        NULL,
        0);
    ReleaseDC(NULL, hdc);
    buf->info = info;
    buf->bkidx = GetNearestPaletteIndex(hpal,RGB(0,0,0));

    return buf;
}

/***************************************************************************************/
/* Clear entire buffer
/***************************************************************************************/
void Offscreen_clear(OFFSCREEN *buf)
{
    FillMemory(
        buf->bits,
        buf->info->biWidth * buf->info->biHeight,
        buf->bkidx);
}

/***************************************************************************************/
/* Clear rectangle
/***************************************************************************************/
void Offscreen_blackness(OFFSCREEN *buf, int x, int y, int cx, int cy)
{
    BYTE *base;
    int   yy;
    long  offset;

    offset = buf->info->biWidth % 4;
    base = buf->bits + x + ((buf->info->biHeight - (y + cy)) * (buf->info->biWidth + offset));
    for (yy = 0; yy < cy; yy++) {
        FillMemory(base, cx, buf->bkidx);
        base += (buf->info->biWidth + offset);
    }
}

/***************************************************************************************/
/* Dealloc offscreen buffer
/***************************************************************************************/
void Offscreen_free(OFFSCREEN *buf)
{
    if (buf) {
        if(buf->hbmp) {
            DeleteObject(buf->hbmp);
        }
        if(buf->info) {
            free(buf->info);
        }
        free(buf);
    }
}

/*--------------------------------------------------------------------------------------
 * Check bitmap file validity
 *-------------------------------------------------------------------------------------*/
static BOOL _checkTileFormat(
    HBITMAP hbmp, /* bitmap filename */
    int     width, /* single tile width */
    int     height /* single tile height */
)
{
    DIBSECTION ds;
    BOOL result = FALSE;
    int xcount, ycount;

    if (hbmp && width > 0 && width <= MAX_TILEWIDTH && height > 0 && height <= MAX_TILEHEIGHT) {

        /* Get number of bits per pixel and check this version support it */
        if (GetObject(hbmp, sizeof(DIBSECTION), &ds)
          && ds.dsBmih.biBitCount == COLOR_BITCOUNT) {

            /* Check tile sizes */
            xcount = ds.dsBmih.biWidth / width;
            ycount = ds.dsBmih.biHeight / height;
            if ((xcount * width) == ds.dsBmih.biWidth
              && (ycount * height) == ds.dsBmih.biHeight) {
                result = TRUE;
            }
        }
    }

    return result;
}

/***************************************************************************************/
/* Create palette from bitmap
/***************************************************************************************/
HPALETTE CreateBitmapPalette(HBITMAP hbmp)
{
    HPALETTE    hpal = NULL;
    HBITMAP     oldBmp;
    HDC         hdc;
    BITMAP      bm;
    LOGPALETTE *logpal;
    RGBQUAD     rgb[256];
    int         i, len;

    /* get the color depth of the DIBSection */
    GetObject(hbmp, sizeof(BITMAP), &bm);

    /* if the DIBSection is 256 color or less, it has a color table */
    if((bm.bmBitsPixel * bm.bmPlanes) <= 8) {

        /* create a memory DC */
        hdc = CreateCompatibleDC(NULL);
        /* select the DIBSection into memory DC */
        oldBmp = SelectObject(hdc, hbmp);
        /* get the DIBSection's color table */
        len = GetDIBColorTable(hdc, 0, 256, rgb);
        /* Create a palette from the color table */
        logpal = (LOGPALETTE*)calloc(1,
            sizeof(LOGPALETTE) + (len * sizeof(PALETTEENTRY)));
        if(logpal) {
            logpal->palVersion = 0x300;
            logpal->palNumEntries = len;
            for(i = 0; i < len; i++) {
                logpal->palPalEntry[i].peRed   = rgb[i].rgbRed;
                logpal->palPalEntry[i].peGreen = rgb[i].rgbGreen;
                logpal->palPalEntry[i].peBlue  = rgb[i].rgbBlue;
                logpal->palPalEntry[i].peFlags = 0;
            }

            /* Create Palette */
            hpal = CreatePalette(logpal);
            /* Clean up */
            free(logpal);
        }

        /* Select previous bitmap */
        SelectObject(hdc, oldBmp);
        /* Delete memory DC */
        DeleteDC(hdc);
    }else {
        /* Get default DC */
        hdc = GetDC(NULL);
        /* Create halftone palette */
        hpal = CreateHalftonePalette(hdc);
        /* Release DC */
        ReleaseDC(NULL, hdc);
    }

    return hpal;
}

#endif /* NH2K_EXTENS */
