//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
#include <windows.h>
#include <tchar.h>
#include <stressutils.h>
#include "dib.h"

HPALETTE ghPal = NULL;
HINSTANCE g_hInstance = NULL;
HWND ghwndMain = NULL;
TCHAR gszClassName[] = TEXT("s2_dib");
int gIndex = 0;

/////////////////////////////////////////////////////////////////////////////////////
static inline BOOL IsValidLocalWindow(HWND hwnd)
{
    if (IsWindow(hwnd))
    {
        DWORD dwWndProcessID = 0;

        if(GetWindowThreadProcessId(hwnd, &dwWndProcessID))
        {
            if (GetCurrentProcessId() == dwWndProcessID)
            {
                return TRUE;
            }
        }
    }

    return FALSE;
}

/////////////////////////////////////////////////////////////////////////////////////
VOID MessagePump(HWND hWnd = NULL)
{
    MSG msg;
    const MSGPUMP_LOOPCOUNT = 10;
    const MSGPUMP_LOOPDELAY = 50; // msec

    for (INT i = 0; i < MSGPUMP_LOOPCOUNT; i++)
    {
        while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE))
        {
            // Do not pump WM_PAINT messages for invalid and non-local windows
            if ((WM_PAINT == msg.message) && !IsValidLocalWindow(msg.hwnd))
            {
                LogWarn2(_T("### MessagePump - WARNING - WM_PAINT for invalid window %x in our queue\r\n"), msg.hwnd);
                return;
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        Sleep(MSGPUMP_LOOPDELAY);
    }
}

BOOL InitApplication( HANDLE hInstance )
{
    WNDCLASS wc;

    memset( &wc, 0, sizeof(WNDCLASS) );
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = (WNDPROC)DefWindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = (HINSTANCE) hInstance;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszClassName = gszClassName;

    return RegisterClass( &wc );
}

BOOL InitInstance( HANDLE hInstance, int nCmdShow )
{
    int cy = GetSystemMetrics(SM_CYSCREEN);
    if (cy < 300)
        cy = 240;

    ghwndMain = CreateWindowEx(
                                                    0,
                                                    gszClassName,
                                                    gszClassName,
                                                    WS_OVERLAPPED|WS_CAPTION|WS_BORDER|WS_VISIBLE,
                                                    rand()%100, 0,400, cy,
                                                    NULL,
                                                    NULL,
                                                    (HINSTANCE) hInstance,
                                                    NULL );
    if( ghwndMain == NULL )
        return FALSE;
    ShowWindow( ghwndMain, nCmdShow );
    UpdateWindow( ghwndMain );
    MessagePump();
    return TRUE;
}


/////////////////////////////////////////////////////////////////////////////////////
BOOL InitializeStressModule (
                            /*[in]*/ MODULE_PARAMS* pmp,
                            /*[out]*/ UINT* pnThreads
                            )
{
    *pnThreads = 1;
    InitializeStressUtils (
                                        _T("s2_dib"),    // Module name to be used in logging
                                        (SLOG_SPACE_GDI << 16) | SLOG_BITMAP | SLOG_DRAW,        // Logging zones used by default
                                        pmp                // Forward the Module params passed on the cmd line
                                        );

    int iSeed = GetTickCount();

    if (pmp->tszUser && !_stscanf(pmp->tszUser, TEXT("%d"), &iSeed))
    {
        iSeed = GetTickCount();
    }
    TCHAR tsz[MAX_PATH];

    GetModuleFileName((HINSTANCE) g_hInstance, tsz, MAX_PATH);
    tsz[MAX_PATH - 1] = 0;

    LogComment(_T("Module File Name: %s"), tsz);

    LogComment(TEXT("Using random seed %d"), iSeed);
    srand(iSeed);

    InitApplication (g_hInstance);
    InitInstance (g_hInstance, SW_SHOWNORMAL);
    return TRUE;
}



/////////////////////////////////////////////////////////////////////////////////////
UINT DoStressIteration (
                        /*[in]*/ HANDLE hThread,
                        /*[in]*/ DWORD dwThreadId,
                        /*[in,out]*/ LPVOID pv /*unused*/)
{
    int width = (rand() % (600-16)) + 16;
    gIndex++;

    UINT uiRet = TestDIBSections( ghwndMain, width, awBPPOpts[rand()%7]);
    MessagePump();
    return uiRet;
}



/////////////////////////////////////////////////////////////////////////////////////
DWORD TerminateStressModule (void)
{
    InvalidateRect(ghwndMain, NULL, TRUE);
    UpdateWindow(ghwndMain);
    MessagePump();

    DestroyWindow(ghwndMain);
    ghwndMain = NULL;
    return ((DWORD) -1);
}

/* **********************************************************************
  REMARKS:
    Creates a DIBSection based on the criteria passed in as parameters.

    The DIBSection is created with a default color table - for 8bpp and
    above, this is a spectrum palette. For 4bpp, it is a stock 16 color
    table, and for 1bpp it is black and white.
********************************************************************** */
HBITMAP DSCreateDIBSection( int nX, int nY, WORD wBits )

{
    HBITMAP         hBitmap = NULL;
    LPBYTE          pBits = NULL;
    LPWORD          pword;
    LPDWORD            pdword;
    int             nInfoSize;
    LPBITMAPINFO    pbmi;
    HDC             hRefDC;
    int             iBand, iRow, i,  nWidthBytes ;
    BITMAP          bm;

    nInfoSize = sizeof( BITMAPINFOHEADER );
    if( wBits <= 8 )
    {
        nInfoSize += sizeof(RGBQUAD) * (1 << wBits);
    }

    if( ( wBits == 16 ) || ( wBits == 32 ) )
        nInfoSize += 3 * sizeof(DWORD);

     // 24 bits: no color table

    // Create the header big enough to contain color table and bitmasks if needed
    pbmi = (LPBITMAPINFO) LocalAlloc(LPTR, nInfoSize );
    memset(pbmi, 0x00, nInfoSize);

    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth = nX;

     // -nY: read from the top left of the bits array
    pbmi->bmiHeader.biHeight = -nY;

    pbmi->bmiHeader.biPlanes = 1;
    pbmi->bmiHeader.biBitCount = wBits;
    pbmi->bmiHeader.biCompression = BI_RGB; // override below for 16 and 32bpp

    if( wBits <= 8 )
    {
        pbmi->bmiHeader.biClrUsed = (1 << wBits);
        pbmi->bmiHeader.biClrImportant = (1 << wBits);
    }


    switch( wBits )
    {
        case 24:// 24bpp requires no special handling
            break;

        case 16:
            {   // if it's 16bpp, fill in the masks and override the compression
                // these are the default masks - you could change them if needed
                LPDWORD pMasks = (LPDWORD)(pbmi->bmiColors);
                pMasks[0] = 0x00007c00;
                pMasks[1] = 0x000003e0;
                pMasks[2] = 0x0000001f;
                pbmi->bmiHeader.biCompression = BI_BITFIELDS;
            }
            break;
        case 32:
            {   // if it's 32bpp, fill in the masks and override the compression
                // these are the default masks - you could change them if needed
                LPDWORD pMasks = (LPDWORD)(pbmi->bmiColors);
                pMasks[0] = 0x00ff0000;
                pMasks[1] = 0x0000ff00;
                pMasks[2] = 0x000000ff;
                pbmi->bmiHeader.biCompression = BI_BITFIELDS;
            }
            break;

        case 8:
            {
                PALETTEENTRY    pe[256];
                    // at this point, prgb points to the color table, even
                    // if bitmasks are present
                ghPal = DSCreateSpectrumPalette();
                if (ghPal)
                {
                    GetPaletteEntries( ghPal, 0, 256, pe );
                    for (i=0;i<256;i++)
                    {
                        pbmi->bmiColors[i].rgbRed = pe[i].peRed;
                        pbmi->bmiColors[i].rgbGreen = pe[i].peGreen;
                        pbmi->bmiColors[i].rgbBlue = pe[i].peBlue;
                        pbmi->bmiColors[i].rgbReserved = 0;
                    }
                }
            }
            break;

        case 4:
            {   // Use a default 16 color table for 4bpp DIBSections
                RGBTRIPLE rgb[16] = { { 0x00, 0x00, 0x00 }, // black
                                      { 0x80, 0x00, 0x00 }, // dark blue
                                      { 0x00, 0x80, 0x00 }, // dark green
                                      { 0x80, 0x80, 0x00 }, // dark cyan
                                      { 0x00, 0x00, 0x80 }, // dark red
                                      { 0x80, 0x00, 0x80 }, // dark magenta
                                      { 0x00, 0x80, 0x80 }, // dark yellow
                                      { 0xC0, 0xC0, 0xC0 }, // light gray
                                      { 0x80, 0x80, 0x80 }, // medium gray
                                      { 0xFF, 0x00, 0x00 }, // blue
                                      { 0x00, 0xFF, 0x00 }, // green
                                      { 0xFF, 0xFF, 0x00 }, // cyan
                                      { 0x00, 0x00, 0xFF }, // red
                                      { 0xFF, 0x00, 0xFF }, // magenta
                                      { 0x00, 0xFF, 0xFF }, // yellow
                                      { 0xFF, 0xFF, 0xFF } };// white

                for(i=0;i<16;i++)
                {
                    pbmi->bmiColors[i].rgbRed = rgb[i].rgbtRed;
                    pbmi->bmiColors[i].rgbGreen = rgb[i].rgbtGreen;
                    pbmi->bmiColors[i].rgbBlue = rgb[i].rgbtBlue;
                    pbmi->bmiColors[i].rgbReserved = 0;
                }
            }
            break;
        case 1: // BW
            pbmi->bmiColors[0].rgbRed = pbmi->bmiColors[0].rgbGreen = pbmi->bmiColors[0].rgbBlue = 0;
            pbmi->bmiColors[1].rgbRed = pbmi->bmiColors[1].rgbGreen = pbmi->bmiColors[1].rgbBlue = 255;
            pbmi->bmiColors[0].rgbReserved = pbmi->bmiColors[1].rgbReserved = 0;
            break;

        case 2: //  4 colors
            pbmi->bmiColors[0].rgbRed = pbmi->bmiColors[0].rgbGreen = pbmi->bmiColors[0].rgbBlue = 0x0;
            pbmi->bmiColors[1].rgbRed = pbmi->bmiColors[1].rgbGreen = pbmi->bmiColors[1].rgbBlue = 0x88;
            pbmi->bmiColors[2].rgbRed = pbmi->bmiColors[2].rgbGreen = pbmi->bmiColors[2].rgbBlue = 0xCC;
            pbmi->bmiColors[3].rgbRed = pbmi->bmiColors[3].rgbGreen = pbmi->bmiColors[3].rgbBlue = 0xFF;

            pbmi->bmiColors[0].rgbReserved = pbmi->bmiColors[1].rgbReserved = 0;
            pbmi->bmiColors[2].rgbReserved = pbmi->bmiColors[3].rgbReserved = 0;
            break;
    }

    SetLastError(ERROR_SUCCESS);

    hRefDC = GetDC( NULL );
    hBitmap = CreateDIBSection( hRefDC, pbmi, DIB_RGB_COLORS, (void**)&pBits, NULL, 0 );
    // Record start byte for later debugging sessions
    LPBYTE pBMPStart = pBits;

    ReleaseDC( NULL, hRefDC );
    hRefDC = NULL;

    if( hBitmap != NULL &&  pBits != NULL )
    {
        // DWORD alignment.
        nWidthBytes = BYTESPERLINE(pbmi->bmiHeader.biWidth,pbmi->bmiHeader.biBitCount*pbmi->bmiHeader.biPlanes);

        // NOTE: bm.bmWidthBytes not DOWRD alignment.
        // if using that value,  color filled in is not correct.
        GetObject(hBitmap, sizeof(BITMAP), &bm);
        LogVerbose(TEXT("width=%d bits=%d: nWidthBytes=%d, bm.bmWidthBytes=%d\r\n"),
            pbmi->bmiHeader.biWidth, wBits, nWidthBytes, bm.bmWidthBytes);

        switch (wBits)
        {
            case 24:  
                {
                int nRowAlign =  nWidthBytes - nX * 3;
                // 4 bands:  Red, green, blue and black
                for (iBand=0; iBand < 4; iBand++)
                {
                    for (iRow=0; iRow < (nY/4); iRow++)
                    {
                        for(int CurrPixel=0; CurrPixel < nX; CurrPixel++)
                        {
                            switch (iBand)
                            {
                                case 0:           // RED
                                    *pBits++ = 0;
                                    *pBits++ = 0;
                                    *pBits++ = 255;
                                    break;
                                case 1:        // Green
                                    *pBits++ = 0;
                                    *pBits++ = 255;
                                    *pBits++ = 0;
                                    break;
                                case 2:        // blue
                                    *pBits++ = 255;
                                    *pBits++ = 0;
                                    *pBits++ = 0;
                                    break;
                                case 3:        // black
                                    *pBits++ = 0;
                                    *pBits++ = 0;
                                    *pBits++ = 0;
                                    break;
                            }
                        }

                        // finish the row (dword aligning ourselves)
                        int nByteLeft = nRowAlign;
                        while (nByteLeft--)
                        {
                            *pBits++ = 0;
                        }
                    }   // pixel loop
                }   // band loop
                }
                break;
            case 1:
                // two band: White and black
                for (iRow = 0; iRow < nY; iRow++)
                {
                    for(i=0; i<nWidthBytes; i++)
                    {
                        *pBits++ = (iRow <= (nY / 2)) ? 0xFF:0;
                    }
                }
                break;
            case 2:   
                // 4 band: white, light gray, dark gray and black
                for (iBand=0; iBand < 4; iBand++)
                {
                    for (iRow=0;  iRow < (nY/4); iRow++)
                    {
                        for(i=0; i < nWidthBytes; i++)
                        {
                            switch (iBand)
                            {
                                case 0:  // white
                                    *pBits++ = 0xFF; break;     //1111=F color table index = 3
                                case 1:  // Light gray
                                    *pBits++ = 0xAA; break;  //1010=A  color table index = 2
                                case 2:  // Dark Gray
                                    *pBits++ = 0x55; break;  //0101=5  color table index = 1
                                case 3:  //    Black
                                    *pBits++ = 0; break;         //0000=0  color table index = 0
                            }
                        }
                   }
                }
                break;
            case 4:
                for (iBand=0; iBand < 16; iBand++)
                {
                    for (iRow=0;  iRow < (nY/16); iRow++)
                    {
                        for(i=0; i < nWidthBytes; i++)
                        {
                            *pBits++ = (iBand << 4) | iBand;
                        }
                    }
                }
                break;

            case 8:  
                // byte value is the index into the color table
                for (iRow=0;  iRow < nY; iRow++)
                {
                    for (i=0; i < nWidthBytes; i++)
                    {
                        *pBits++ = iRow;
                    }
                }
                break;

            case 16:
                // 2 bytes/per pixel:
                //pbmi->bmiHeader.biCompression = BI_BITFIELDS
                pword = (PWORD) pBits;
                for (iBand=0; iBand < 4; iBand++)
                {
                    for (iRow=0;  iRow < (nY/4); iRow++)
                    {
                        for(i=0; i < nWidthBytes / 2; i++)
                        {
                            switch (iBand)
                            {
                                case 0:  // RED:
                                    *pword++ = 0x7C00; break;
                                case 1:  // //Green:
                                    *pword++ = 0x3E0; break;
                                case 2:  // //Blue:
                                    *pword++ = 0x001F; break;
                                case 3:  // //Black:
                                    *pword++ = 0; break;
                            }
                        }
                    }
                }
                break;
            case 32:
                // 4 bytes/per pixel:
                //pbmi->bmiHeader.biCompression = BI_BITFIELDS
                pdword = (LPDWORD) pBits;
                for (iBand=0; iBand < 4; iBand++)
                {
                    for (iRow=0;  iRow < (nY/4); iRow++)
                    {
                        for(i=0; i < nWidthBytes / 4; i++)
                        {
                            switch (iBand)
                            {
                                case 0:  // RED:
                                    *pdword++ = 0xFF0000;
                                    break;
                                case 1:  // //Green:
                                    *pdword++ = 0xFF00;
                                    break;
                                case 2:  // //Blue:
                                    *pdword++ = 0x00FF;
                                    break;
                                case 3:  // //Black:
                                    *pdword++ = 0;
                                    break;
                            }
                        }
                    }
                }
                break;
        }
    }

    LocalFree( pbmi );
    return hBitmap;
}


/* **********************************************************************
  HPALETTE DSCreateSpectrumPalette( void )
  RETURNS:
    HPALETTE - a handle to a spectrum palette - NULL on failure

    This function will build a palette with a spectrum of colors.  It is
    useful when you want to display a number of DIBs each with a different
    palette yet still have an a good selection of colors to which the
    DIBs' colors will be mapped.
********************************************************************** */
HPALETTE WINAPI DSCreateSpectrumPalette( void )
{
    HPALETTE hPal;
    LPLOGPALETTE lplgPal;
    BYTE red, green, blue;
    int i, fGrayScale ;
    static int iCalled = 0;

    if (iCalled == 2)
        LogVerbose(TEXT("iCalled=%d: create a palette with a spectrum of colors\r\n"), iCalled);
    else
        LogVerbose(TEXT("iCalled=%d: create a palette with gray scale\r\n"), iCalled);


    lplgPal = (LPLOGPALETTE)LocalAlloc( LPTR, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * 256 );
    if (!lplgPal)
    {
        LogFail(TEXT("iCalled=%d: DSCreateSpectrumPalette: LocalAlloc failed: err=%ld\r\n"), iCalled, GetLastError());
        return NULL;
    }

    lplgPal->palVersion = 0x300;
    lplgPal->palNumEntries = 256;

    red = green = blue = 0;
    // special test case for case 1. Don't select the gray palette into screen DC
    if (iCalled == 0)
    {
        fGrayScale  = TRUE;
    }
    else
    {
        fGrayScale  = iCalled % 2;
    }

    for (i = 0; i < 256; i++)
    {
        lplgPal->palPalEntry[i].peRed   = red;
        lplgPal->palPalEntry[i].peGreen = green;
        lplgPal->palPalEntry[i].peBlue  = blue;

        if (fGrayScale)
        {
            red++;
            green++;
            blue++;
        }
        else
        {

            if (!(red += 32))
                 if (!(green += 32))
                      blue += 64;
        }
    }

    if ((hPal = CreatePalette(lplgPal)) == NULL)
    {
         LogFail(TEXT("iCalled=%d: DSCreateSpectrumPalette: CreatePalette: failed: err=%ld\r\n"), iCalled, GetLastError());
    }

    LocalFree((HLOCAL)lplgPal);
    iCalled++;
    return hPal;
}



/* ******************************************************************* */
UINT TestDIBSections( HWND hWndParent, int width, WORD wBits )
{
    int     nRet, nDCBits, fSupportPalette;
    HBITMAP hBitmap, hOldBitmap;
    HDC      hMemDC, hDC;
    TCHAR   szDebug[64];
    HPALETTE  hPalSav = NULL; // for 8 bits DIB bitmap
    static int iShow8bits = 0;      // for 8 bits DIB bitmap
    UINT uiRet = CESTRESS_PASS;


    wsprintf(szDebug, TEXT("test=%d width=%d bits=%d"), gIndex+1, width, wBits);
    //SetWindowText(hWndParent, szDebug);

    hDC = GetDC(hWndParent);
    // Check if this DC support Palette:
    nDCBits = GetDeviceCaps(hDC,BITSPIXEL);
    nRet = GetDeviceCaps(hDC,RASTERCAPS);

    fSupportPalette =  (nRet & RC_PALETTE);
    LogVerbose(TEXT("DC = %d bits: %s palette.-----\r\n"), nDCBits, 
        (LPTSTR) (fSupportPalette? TEXT("Support") : TEXT("NOT Support")));

    if (fSupportPalette  && (SelectPalette(hDC, (HPALETTE)GetStockObject(DEFAULT_PALETTE ), FALSE) == NULL))
    {
        LogWarn1(TEXT("System supports Palette: But SelectPalette() failed: err=%ld.-----\r\n"),
            GetLastError());
        uiRet = CESTRESS_WARN1;
    }

    if (fSupportPalette   && (RealizePalette(hDC) == GDI_ERROR))
    {
        LogWarn1(TEXT("System supports Palette: But RealizePalette() failed: err=%ld.-----\r\n"),
            GetLastError());
        uiRet = CESTRESS_WARN1;
    }

    hBitmap = DSCreateDIBSection( width, width, wBits );
    if (!hBitmap)
    {
        if(GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
        {
            LogWarn1(TEXT("DSCreateDIBSection failed with insufficient memory"));
            ReleaseDC(hWndParent, hDC);
            return CESTRESS_WARN1;
        }
        else
        {
            LogFail(TEXT("DSCreateDIBSection failed"));
            ReleaseDC(hWndParent, hDC);
            return CESTRESS_FAIL;
        }
    }

    hMemDC = CreateCompatibleDC( hDC );
    if (!hMemDC)
    {
        LogWarn1(TEXT("CreateCompatibleDC failed, GLE: %d"), GetLastError());
        uiRet = CESTRESS_WARN1;
    }
    hOldBitmap = (HBITMAP) SelectObject( hMemDC, hBitmap );
    if (wBits == 8 && (GetForegroundWindow() == hWndParent))
    {
        //iShow8bits == 0: don't select the palette to screen DC
        if (iShow8bits > 0)
        {
            if ((hPalSav = SelectPalette(hDC, ghPal, FALSE)) == NULL)
            {
                LogFail(TEXT("nDC bits=%d: display 8bit bitmap:  SelectPalette(hDC, ghPal) failed: err=%ld\r\n"),
                    nDCBits,GetLastError());
                uiRet = CESTRESS_FAIL;
            }

            if (RealizePalette(hDC) == GDI_ERROR)
            {
                LogFail(TEXT("nDC bits=%d: display 8bit bitmap:  RelizePalette(hDC) failed: err=%ld\r\n"),
                    nDCBits,GetLastError());
                uiRet = CESTRESS_FAIL;
            }
            else
            {
                LogVerbose(TEXT("nDC bits=%d: display 8bit bitmap:   Select and RelizePalette(hDC) Succeeded\r\n"),
                    nDCBits);
            }
        }
        iShow8bits++;
    }

    BitBlt( hDC, 10, 0, width, width, hMemDC, 0, 0, SRCCOPY );
    SelectObject( hMemDC, hOldBitmap );
    DeleteDC( hMemDC );
    DeleteObject( hBitmap );
    if (wBits == 8)
    {
        if (iShow8bits > 0)
            SelectPalette(hDC, hPalSav, FALSE);
        DeleteObject( ghPal );
    }
    ReleaseDC(hWndParent, hDC);
    CheckMemory();
    return uiRet;
}

void CheckMemory()
{
    static DWORD s_dwAvailVirtual = 0xffffffff;
    MEMORYSTATUS ms;

    ms.dwLength = sizeof(MEMORYSTATUS);
    GlobalMemoryStatus(&ms);
    if (ms.dwAvailVirtual < s_dwAvailVirtual)
    {
        LogComment(TEXT("Memory Usage now at %d"), ms.dwTotalVirtual - ms.dwAvailVirtual);
        s_dwAvailVirtual = ms.dwAvailVirtual;
    }
}

///////////////////////////////////////////////////////////
BOOL WINAPI DllMain(
                    HANDLE hInstance,
                    ULONG dwReason,
                    LPVOID lpReserved
                    )
{
    g_hInstance = (HINSTANCE) hInstance;
    return TRUE;
}
