#include <windows.h>

#include "expdef.h"
#include "explib.h"
#include "display.h"

#include "dib.h"    // to make sure prototypes match

//
// Raster ops used in drawing
//

#define DSa     0x008800C6L
#define DSx     0x00660046L
#define DSo     0x00EE0086L
#define DSna    0x00220326L

//
// RGB color values
//

#define rgbWhite    RGB(255,255,255)
#define rgbBlack    RGB(0,0,0)

//
// Flags used for the Redraw function
//

#define UPDATE_SCREEN   TRUE
#define NO_UPDATE       FALSE

//
// update system
//

#define WAIT_LOOP               // comment out to use timer ticks
#ifndef WAIT_LOOP
#define TIMER_TICKS
#endif

#define ASSERT(exp) \
    if(!(exp)) perr("oioi %s %d",__FILE__,__LINE__)

//
// Various types
//

typedef char huge *HPSTR;
typedef BYTE huge *HPBYTE;

//
// memory allocation macros
//

HANDLE __hMem;

#define ALLOCATE(s) ((__hMem = GlobalAlloc(GHND, (DWORD)(s))) ? GlobalLock(__hMem) : NULL)
#define FREE(p) (GlobalUnlock((HANDLE)HIWORD(p)),GlobalFree((HANDLE)HIWORD(p)))
#define SIZE(p) (GlobalSize((HANDLE)HIWORD(p)))

//
// Load a DIB file.
// The DIB structure pointed to by pDIB is filled in with the info
// on the DIB.  A single memory chunk is used for the whole DIB
// keeping the DIB in CF_DIB (packed DIB) format.
//

LPBITMAPINFO LoadDIB(LPSTR pszPath)
{
    char szFile[_MAX_PATH];
    int fd;
    OFSTRUCT os;
    BITMAPFILEHEADER BmpFileHdr;
    BITMAPINFOHEADER BmpInfoHdr;
    BITMAPCOREHEADER BmpCoreHdr;
    WORD wColors, wColorTableSize, wBytes, w;
    DWORD dwBISize, dwBitsSize, dwBytes, dwSize;
    LPBITMAPINFO pBmpInfo = NULL;
    HPSTR pBits = NULL;
    BOOL bIsPM = FALSE;
    RGBTRIPLE rgbt;
    LPRGBQUAD lpRGB;

    lstrcpy(szFile, pszPath);

    //
    // try to open the file in read mode
    //

    fd = OpenFile(szFile, &os, OF_READ);
    if (fd < 1) {
        dmsg("Failed to open %s", (LPSTR)szFile);
        goto xabort;
    }

    dmsg("Loading: %s", (LPSTR)szFile);

    //
    // read the file header to get the file size and to
    // find where the bits start in the file
    //

    wBytes = _lread(fd, (LPSTR)&BmpFileHdr, sizeof(BmpFileHdr));
    if (wBytes != sizeof(BmpFileHdr)) {
        dmsg("Failed to read file header");
        goto xabort;
    }

    //
    // check we have the magic 'BM' at the start
    //

    if (BmpFileHdr.bfType != 0x4D42) {
        dmsg("Not a bitmap file");
        goto xabort;
    }

    //
    // make a wild guess that the file is in Windows DIB
    // format and read the BITMAPINFOHEADER.  If it turns
    // out to be a PM DIB file we'll convert it later.
    //

    wBytes = _lread(fd, (LPSTR)&BmpInfoHdr, sizeof(BmpInfoHdr));
    if (wBytes != sizeof(BmpInfoHdr)) {
        dmsg("Failed to read BITMAPINFOHEADER");
        goto xabort;
    }

    //
    // check we got a real Windows DIB file
    //

    if (BmpInfoHdr.biSize != sizeof(BITMAPINFOHEADER)) {
        dmsg("File is not Windows DIB format");
        if (BmpInfoHdr.biSize != sizeof(BITMAPCOREHEADER)) {
            dmsg("File is not Windows or PM DIB format");
            goto xabort;
        }

        //
        // set a flag to convert PM file to Win format later
        //

        bIsPM = TRUE;

        //
        // back up the file pointer and read the BITMAPCOREHEADER
        // and create the BITMAPINFOHEADER from it
        //

        _llseek(fd, sizeof(BITMAPFILEHEADER), SEEK_SET);

        wBytes = _lread(fd, (LPSTR)&BmpCoreHdr, sizeof(BmpCoreHdr));
        if (wBytes != sizeof(BmpCoreHdr)) {
            dmsg("Failed to read BITMAPCOREHEADER");
            goto xabort;
        }

        BmpInfoHdr.biSize = sizeof(BITMAPINFOHEADER);
        BmpInfoHdr.biWidth = (DWORD) BmpCoreHdr.bcWidth;
        BmpInfoHdr.biHeight = (DWORD) BmpCoreHdr.bcHeight;
        BmpInfoHdr.biPlanes = BmpCoreHdr.bcPlanes;
        BmpInfoHdr.biBitCount = BmpCoreHdr.bcBitCount;
        BmpInfoHdr.biCompression = BI_RGB;
        BmpInfoHdr.biSizeImage = 0;
        BmpInfoHdr.biXPelsPerMeter = 0;
        BmpInfoHdr.biYPelsPerMeter = 0;
        BmpInfoHdr.biClrUsed = 0;
        BmpInfoHdr.biClrImportant = 0;

    }

    //
    // ok so we got a real DIB file so work out
    // how much memory we need for the BITMAPINFO
    // structure, color table and bits.  Allocate it,
    // copy the BmpInfoHdr we have so far
    // and then read in the color table from the file
    //

    wColors = NumDIBColorEntries((LPBITMAPINFO) &BmpInfoHdr);
    wColorTableSize = wColors * sizeof(RGBQUAD);
    dwBitsSize = BmpFileHdr.bfSize - BmpFileHdr.bfOffBits;
    dwBISize = (DWORD) sizeof(BITMAPINFOHEADER)
           + (DWORD) wColorTableSize;
    dwSize = dwBISize + dwBitsSize;

    if (dwSize > 65536L) {
        dmsg("WARNING: DIB > 64k");
    }

    //
    // allocate and lock the memory
    //

    pBmpInfo = (LPBITMAPINFO) ALLOCATE(dwSize);
    if (!pBmpInfo) {
        dmsg("Out of memory for DIB");
        goto xabort;
    }

    //
    // copy the header we already have
    //

    hmemcpy((HPSTR) pBmpInfo, (HPSTR) &BmpInfoHdr, sizeof(BITMAPINFOHEADER));

    //
    // now read the color table in from the file
    //

    if (bIsPM == FALSE) {

        //
        // read the color table from the file
        //

        wBytes = _lread(fd,
                        ((LPSTR) pBmpInfo) + sizeof(BITMAPINFOHEADER),
                        wColorTableSize);

        if (wBytes != wColorTableSize) {
            dmsg("Failed to read color table");
            goto xabort;
        }

    } else {

        //
        // read each color table entry in turn and convert it
        // to Win DIB format as we go
        //

        lpRGB = (LPRGBQUAD) ((LPSTR) pBmpInfo + sizeof(BITMAPINFOHEADER));
        for (w=0; w<wColors; w++) {
            wBytes = _lread(fd, (LPSTR) &rgbt, sizeof(RGBTRIPLE));
            if (wBytes != sizeof(RGBTRIPLE)) {
                dmsg("Failed to read RGBTRIPLE");
                goto xabort;
            }
            lpRGB->rgbBlue = rgbt.rgbtBlue;
            lpRGB->rgbGreen = rgbt.rgbtGreen;
            lpRGB->rgbRed = rgbt.rgbtRed;
            lpRGB->rgbReserved = 0;
            lpRGB++;
        }
    }

    //
    // now we just have to read the bits from the file
    //

    pBits = (LPSTR) pBmpInfo
          + sizeof(BITMAPINFOHEADER)
          + wColors * sizeof(RGBQUAD);

    //
    // seek to the bits in the file
    //

    _llseek(fd, BmpFileHdr.bfOffBits, SEEK_SET);

    //
    // read the bits
    //

    dwBytes = _hread(fd, pBits, dwBitsSize);
    if (dwBytes != dwBitsSize) {
        dmsg("Failed to read bits");
        goto xabort;
    }

    //
    // done with the file
    //

    _lclose(fd);


    //
    // make sure it's a 256 color DIB
    //

    if (pBmpInfo->bmiHeader.biBitCount != 8) {
        dmsg( "%u bit DIBs are not supported", (UINT)pBmpInfo->bmiHeader.biBitCount);
        goto xabort;
    }

    //
    // make sure it's not RLE
    //

    if (pBmpInfo->bmiHeader.biCompression != BI_RGB) {
        dmsg( "Compressed (RLE) DIBs are not supported");
        goto xabort;
    }

    //
    // Copy the return info
    //

    return (LPBITMAPINFO) pBmpInfo;


xabort: // crap out

    if (pBmpInfo) FREE(pBmpInfo);
    if (fd >= 1) _lclose(fd);

    dmsg( "Unable to load file");

    return NULL;
}

//
// Delete a DIB
//

void DeleteDIB(LPBITMAPINFO pDIB)
{
    if (!pDIB) return;

    FREE(pDIB);

}

//
// Check for a windows DIB
//

BOOL IsWinDIB(LPBITMAPINFOHEADER pBIH)
{
    if (pBIH->biSize != sizeof(BITMAPINFOHEADER)) {
        return FALSE;
    }
    return TRUE;
}

WORD NumDIBColorEntries(LPBITMAPINFO lpBmpInfo)
{
    LPBITMAPINFOHEADER lpBIH;
    LPBITMAPCOREHEADER lpBCH;
    WORD wColors, wBitCount;

    if (!lpBmpInfo) {
                dmsg("NULL arg to NumDIBColorEntries()");
        return 0;
    }

    lpBIH = &(lpBmpInfo->bmiHeader);
    lpBCH = (LPBITMAPCOREHEADER) lpBIH;

    //
    // start off by assuming the color table size from
    // the bit per pixel field
    //

    if (IsWinDIB(lpBIH)) {
        wBitCount = lpBIH->biBitCount;
    } else {
        wBitCount = lpBCH->bcBitCount;
    }

    switch (wBitCount) {
    case 1:
        wColors = 2;
        break;
    case 4:
        wColors = 16;
        break;
    case 8:
        wColors = 256;
        break;
    case 24:
    default:
        wColors = 0;
        break;
    }

    //
    // If this is a Windows DIB, then the color table length
    // is determined by the biClrUsed field
    //

    if (IsWinDIB(lpBIH)) {
        if (lpBIH->biClrUsed != 0) {
            wColors = (WORD)lpBIH->biClrUsed;
        }
    }

    return wColors;
}

//
// The the value (color index) of a DIB pixel
// NOTE: DIB scan lines are DWORD aligned.  The scan line
// storage width may be wider than the scan line image width
// so calc the storage width by rounding the image width
// to the next highest dword value
//

HPSTR GetDIBPixelAddress(LPBITMAPINFO pDIB, cpix x, cpix y)
{
    HPSTR p;
    long lWidth;

    //
    // make sure it's in range and if not return zero
    //

    if ((x < 0)
    || (y < 0)
    || (x >= DIB_WIDTH(pDIB))
    || (y >= DIB_HEIGHT(pDIB))) {
        perr("off-bound pix addr: %ld %ld (bounds are %ld %ld)",
            (long)x,(long)y,(long)DIB_WIDTH(pDIB),(long)DIB_HEIGHT(pDIB));
        return NULL;
    }

    //
    // Calculate the scan line storage width
    //

    lWidth = DIB_STORAGEWIDTH(pDIB);

    ASSERT(lWidth <= DIB_WIDTH(pDIB) + 3);
    ASSERT(lWidth >= DIB_WIDTH(pDIB));

    p = (HPSTR) DIB_PBITS(pDIB);
    p += (long)(DIB_HEIGHT(pDIB)-y-1) * lWidth + (long)x;

    return p;
}

//
// The the value (color index) of a DIB pixel
//

short GetDIBPixelValue(LPBITMAPINFO pDIB, cpix x, cpix y)
{
    HPSTR p;

    p = GetDIBPixelAddress(pDIB, x, y);

    if (!p)
        return -1;

    return *(uchar HUGE *)p;
}

rcode SetDIBPixelValue(LPBITMAPINFO pDIB, cpix x, cpix y,uchar val)
{
    HPSTR p;

    p = GetDIBPixelAddress(pDIB, x, y);

    if (!p)
        return INDATA;

    *p = val;
    return OK;
}

//
// Return the Color of a DIB pixel
//

COLORREF GetDIBPixelColor(LPBITMAPINFO pDIB, cpix x, cpix y)
{
    short    value;
    RGBQUAD  quad;
    COLORREF clr;

    if((value = GetDIBPixelValue(pDIB, x, y)) < 0)
        return 0xFFFFFFFF;

    quad  = (DIB_PCLRTAB(pDIB))[(uchar)value];
    clr   = RGB(quad.rgbRed, quad.rgbGreen, quad.rgbBlue);

    return clr;
}

void DumpDIB(LPBITMAPINFO pDIB)
{
    uchar buf[256];
    int x,y;
    for(y=0;y<16;y++)
    for(x=0;x<16;x++)
        buf[y*16+x] = GetDIBPixelValue(pDIB,x,y);
    explib_root.hexdump(buf, 256, dmsg);
}

void DumpDIBRGB(LPBITMAPINFO pDIB)
{
    COLORREF buf[256];
    int x,y;
    for(y=0;y<16;y++)
    for(x=0;x<16;x++)
        buf[y*16+x] = GetDIBPixelColor(pDIB,x,y);
    explib_root.hexdump(buf, 256*4, dmsg);
}

//
// Create a DIB the same size and organization as another one
//

LPBITMAPINFO CreateCompatibleDIB(LPBITMAPINFO pOld)
{
    LPBITMAPINFO pNew;

    pNew = (LPBITMAPINFO)ALLOCATE(SIZE(pOld));

    if (!pNew) {
        perr("No memory for new DIB");
        return NULL;
    }

    //
    // Copy the bitmap info 
    //

    _fmemcpy(pNew,
             pOld, 
             DIB_BISIZE(pOld));

    return pNew;
}
