#include <graphics/bitmap.h>

struct DIBINFO : public BITMAPINFO
{
    RGBQUAD arColors[255]; // Color table info - adds an extra 255 entries to palette
    
    operator LPBITMAPINFO()
    {
        return (LPBITMAPINFO) this;
    }
    
    operator LPBITMAPINFOHEADER()
    {
        return &bmiHeader;
    }
 
    RGBQUAD* ColorTable()
    {
        return bmiColors;
    }
};

namespace Graphics
{

HMODULE Bitmap::hShDll = 0;
Bitmap::ImgLdFunc Bitmap::SHLoadImageFile = 0;
FILE* Bitmap::log = 0;

Bitmap::Bitmap(const WCHAR* fileName)
    : w(0), h(0), bits(0), handle(0), hDIB(0)
{
    if (hShDll == 0)
    {
        fprintf(log, "Loading aygshell.dll\n");
        hShDll = LoadLibrary(TEXT("aygshell.dll"));
    }
    if (hShDll == 0)
    {
        fprintf(log, "Cannot load library");
        return;
    }

    fprintf(log, "Loading SHLoadImageFile function\n");
    SHLoadImageFile = (ImgLdFunc)GetProcAddress(hShDll, TEXT("SHLoadImageFile"));
    if (SHLoadImageFile)
    {
        fprintf(log, "Loading image\n");
        handle = SHLoadImageFile(fileName);
        if (handle)
        {
            BITMAP bm;
            GetObject(handle, sizeof(BITMAP), &bm);
            w = bm.bmWidth;
            h = bm.bmHeight;
            fprintf(log, "Image loaded; dims: %d x %d\n", w, h);
        }
        else
            fprintf(log, "Cannot load image file\n");
    }
    else
        fprintf(log, "Cannot load function\n");
}

Bitmap::Bitmap(HWND hWnd, int x, int y, int width, int height)
    : w(width), h(height), bits(0), handle(0), hDIB(0)
{
    HDC dc = ::GetDC(hWnd);
    handle = CreateCompatibleBitmap(dc, width, height);
    capture(dc, x, y);
    ::ReleaseDC(hWnd, dc);
}

Bitmap::Bitmap(Bitmap& b, int x, int y, int width, int height)
    : w(width), h(height), bits(0), handle(0), hDIB(0)
{
    HDC dc = ::GetDC(NULL);
    handle = CreateCompatibleBitmap(dc, width, height);
    HDC hMem = CreateCompatibleDC(dc);
    ::ReleaseDC(NULL, dc);
    HBITMAP pBmp = (HBITMAP)SelectObject(hMem, b);
    capture(hMem, x, y);
    SelectObject(hMem, pBmp);
    DeleteDC(hMem);
}

void Bitmap::capture(HDC dc, int x, int y)
{
    HDC hMem = CreateCompatibleDC(dc);
    HBITMAP pBmp = (HBITMAP)SelectObject(hMem, handle);
    BitBlt(hMem, 0, 0, w, h, dc, x, y, SRCCOPY);
    SelectObject(hMem, pBmp);
    DeleteDC(hMem);
}

void Bitmap::blend(Bitmap& b, int x, int y, unsigned char alpha, unsigned transpColor, 
    unsigned opaqueColor)
{
    fprintf(log, "blending params x: %d, y: %d, w: %d, h: %d\n", x, y, b.getWidth(), b.getHeight());
    int lnW = x + b.getWidth() <= w ? b.getWidth() : w - x;
    int lines = y + b.getHeight() <= h ? b.getHeight() : h - y;
    fprintf(log, "Line width: %d, lines: %d\n", lnW, lines);

    int offset = (h - y - lines)*w + x;
    int soffs = b.getHeight() - lines;
    
    RGBX* sbits = b.getBits();
    //fprintf(log, "src bits: %s\n", sbits ? "OK" : ":(");
    
    if (alpha > 0) // blend with constant alpha
    {
        int a1 = 255 - alpha;
        for (int l(0); l < lines; ++l)
        {
            for (int i(0), so(soffs), to(offset); i < lnW; ++i)
            {
                RGBX px = sbits[so++];
                if (px == transpColor)
                    to++;
                else
                {
                    if (px == opaqueColor)
                    {
                        bits[to++] = px;
                    }
                    else
                    {
                        int br = px & 0xFF;
                        int bg = (px >> 8) & 0xFF;
                        int bb = (px >> 16) & 0xFF;
                        
                        RGBX dpx = bits[to];
                        int r = (a1 * (dpx & 0xFF) + alpha * br) / 255;
                        int g = (a1 * ((dpx >> 8) & 0xFF) + alpha * bg) / 255;
                        int b = (a1 * ((dpx >> 16) & 0xFF) + alpha * bb) / 255;
                        
                        bits[to++] = r | (g << 8) | (b << 16);
                    }
                }
            }
            offset += w;
            soffs += b.getWidth();
        }
    }
    else    // alpha = 0 -> blend with per pixel alpha
    {
        for (int l(0); l < lines; ++l)
        {
            for (int i(0), so(soffs), to(offset); i < lnW; ++i)
            {
                RGBX px = sbits[so++];
                int palpha = (px >> 24) & 0xFF;
                if (palpha == 0)
                    to++;
                else
                {
                    int br = px & 0xFF;
                    int bg = (px >> 8) & 0xFF;
                    int bb = (px >> 16) & 0xFF;
                    
                    int a1 = 255 - palpha;
                    
                    RGBX dpx = bits[to];
                    int r = (a1 * (dpx & 0xFF) + palpha * br) / 255;
                    int g = (a1 * ((dpx >> 8) & 0xFF) + palpha * bg) / 255;
                    int b = (a1 * ((dpx >> 16) & 0xFF) + palpha * bb) / 255;
                    
                    bits[to++] = r | (g << 8) | (b << 16);
                }
            }
            offset += w;
            soffs += b.getWidth();
        }
    }
}

RGBX* Bitmap::getBits()
{
    if (bits)
        return bits;
        
    DIBINFO dibInfo;
    dibInfo.bmiHeader.biBitCount = 32;
    dibInfo.bmiHeader.biClrImportant = 0;
    dibInfo.bmiHeader.biClrUsed = 0;
    dibInfo.bmiHeader.biCompression = 0;
    dibInfo.bmiHeader.biHeight = h;
    dibInfo.bmiHeader.biPlanes = 1;
    dibInfo.bmiHeader.biSize = 40;
    dibInfo.bmiHeader.biSizeImage = w * h * 4;
    dibInfo.bmiHeader.biWidth = w;
    dibInfo.bmiHeader.biXPelsPerMeter = 3780;
    dibInfo.bmiHeader.biYPelsPerMeter = 3780;
    dibInfo.bmiColors[0].rgbBlue = 0;
    dibInfo.bmiColors[0].rgbGreen = 0;
    dibInfo.bmiColors[0].rgbRed = 0;
    dibInfo.bmiColors[0].rgbReserved = 0;
    
    HDC dc = ::GetDC(NULL);
    void *pBuffer;
    hDIB = CreateDIBSection(dc, (const BITMAPINFO*)dibInfo, DIB_RGB_COLORS, (void**)&pBuffer, NULL, 0);

    HDC srcmDC = CreateCompatibleDC(dc);
    HDC dstmDC = CreateCompatibleDC(dc);
    HBITMAP hB1 = (HBITMAP)::SelectObject(srcmDC, handle); 
    HBITMAP hB2 = (HBITMAP)::SelectObject(dstmDC, hDIB);
    BitBlt(dstmDC, 0, 0, w, h, srcmDC, 0, 0, SRCCOPY);
    ::SelectObject(srcmDC, hB1); 
    ::SelectObject(dstmDC, hB2);
    DeleteDC(srcmDC);
    DeleteDC(dstmDC);
    
    ::ReleaseDC(NULL, dc);
    
    bits = (RGBX*)pBuffer;
    
    return bits;
}

void Bitmap::draw(HDC dc)
{
    HDC memDC = CreateCompatibleDC(dc);
    HBITMAP pb = (HBITMAP)SelectObject(memDC, handle);
    BitBlt(dc, 0, 0, w, h, memDC, 0, 0, SRCCOPY);
    SelectObject(memDC, pb);
    DeleteDC(memDC);
}

void Bitmap::draw(HDC dc, int x, int y)
{
    HDC memDC = CreateCompatibleDC(dc);
    HBITMAP pb = (HBITMAP)SelectObject(memDC, handle);
    BitBlt(dc, x, y, w, h, memDC, 0, 0, SRCCOPY);
    SelectObject(memDC, pb);
    DeleteDC(memDC);
}


void Bitmap::drawBits(HDC dc, int x, int y)
{
    BITMAPINFOHEADER bi;
    ZeroMemory(&bi, sizeof(BITMAPINFOHEADER));
    bi.biSize        = sizeof(BITMAPINFOHEADER);
    bi.biWidth       = w;
    bi.biHeight      = h;
    bi.biPlanes      = 1;
    bi.biBitCount    = 32;
    bi.biCompression = BI_RGB;
    
    SetDIBitsToDevice(dc, x, y, w, h, 0, 0, 0, h, bits, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
}

void Bitmap::drawBits(HDC dc, int x, int y, int w, int h, RGBX* pixels)
{
    BITMAPINFOHEADER bi;
    ZeroMemory(&bi, sizeof(BITMAPINFOHEADER));
    bi.biSize        = sizeof(BITMAPINFOHEADER);
    bi.biWidth       = w;
    bi.biHeight      = h;
    bi.biPlanes      = 1;
    bi.biBitCount    = 32;
    bi.biCompression = BI_RGB;
    
    SetDIBitsToDevice(dc, x, y, w, h, 0, 0, 0, h, pixels, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
}

} // namespace Graphics

