/* See LICENSE.txt for the full license governing this code. */
/**
 * \file windows_screenshot.c
 *
 * Source file for the screenshot API on windows.
 */

#include "SDL_visualtest_process.h"
#include <SDL.h>
#include <SDL_test.h>

#if defined(__CYGWIN__)
#include <sys/stat.h>
#endif

#if defined(__WIN32__)
#include <windows.h>

void LogLastError(const char* str);

static int img_num;
static SDL_ProcessInfo screenshot_pinfo;

/* Saves a bitmap to a file using hdc as a device context */
static int
SaveBitmapToFile(HDC hdc, HBITMAP hbitmap, char* filename)
{
    BITMAP bitmap;
    BITMAPFILEHEADER bfh;
    BITMAPINFOHEADER bih;
    DWORD bmpsize, bytes_written;
    HANDLE hdib, hfile;
    char* bmpdata;
    int return_code = 1;

    if(!hdc)
    {
        SDLTest_LogError("hdc argument is NULL");
        return 0;
    }
    if(!hbitmap)
    {
        SDLTest_LogError("hbitmap argument is NULL");
        return 0;
    }
    if(!filename)
    {
        SDLTest_LogError("filename argument is NULL");
        return 0;
    }

    if(!GetObject(hbitmap, sizeof(BITMAP), (void*)&bitmap))
    {
        SDLTest_LogError("GetObject() failed");
        return_code = 0;
        goto savebitmaptofile_cleanup_generic;
    }
    
    bih.biSize = sizeof(BITMAPINFOHEADER);
    bih.biWidth = bitmap.bmWidth;
    bih.biHeight = bitmap.bmHeight;
    bih.biPlanes = 1;
    bih.biBitCount = 32;
    bih.biCompression = BI_RGB;
    bih.biSizeImage = 0;
    bih.biXPelsPerMeter = 0;
    bih.biYPelsPerMeter = 0;
    bih.biClrUsed = 0;
    bih.biClrImportant = 0;

    bmpsize = ((bitmap.bmWidth * bih.biBitCount + 31) / 32) * 4 * bitmap.bmHeight;

    hdib = GlobalAlloc(GHND, bmpsize);
    if(!hdib)
    {
        LogLastError("GlobalAlloc() failed");
        return_code = 0;
        goto savebitmaptofile_cleanup_generic;
    }
    bmpdata = (char*)GlobalLock(hdib);
    if(!bmpdata)
    {
        LogLastError("GlobalLock() failed");
        return_code = 0;
        goto savebitmaptofile_cleanup_hdib;
    }

    if(!GetDIBits(hdc, hbitmap, 0, (UINT)bitmap.bmHeight, bmpdata,
                  (LPBITMAPINFO)&bih, DIB_RGB_COLORS))
    {
        SDLTest_LogError("GetDIBits() failed");
        return_code = 0;
        goto savebitmaptofile_cleanup_unlockhdib;
    }

    hfile = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                       FILE_ATTRIBUTE_NORMAL, NULL);
    if(hfile == INVALID_HANDLE_VALUE)
    {
        LogLastError("CreateFile()");
        return_code = 0;
        goto savebitmaptofile_cleanup_unlockhdib;
    }
    bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bfh.bfSize = bmpsize + bfh.bfOffBits;
    bfh.bfType = 0x4D42;

    bytes_written = 0;
    if(!WriteFile(hfile, (void*)&bfh, sizeof(BITMAPFILEHEADER), &bytes_written, NULL) ||
       !WriteFile(hfile, (void*)&bih, sizeof(BITMAPINFOHEADER), &bytes_written, NULL) ||
       !WriteFile(hfile, (void*)bmpdata, bmpsize, &bytes_written, NULL))
    {
        LogLastError("WriteFile() failed");
        return_code = 0;
        goto savebitmaptofile_cleanup_hfile;
    }

savebitmaptofile_cleanup_hfile:
    CloseHandle(hfile);

/* make the screenshot file writable on cygwin, since it could be overwritten later */
#if defined(__CYGWIN__)
    if(chmod(filename, 0777) == -1)
    {
        SDLTest_LogError("chmod() failed");
        return_code = 0;
    }
#endif

savebitmaptofile_cleanup_unlockhdib:
    GlobalUnlock(hdib);

savebitmaptofile_cleanup_hdib:
    GlobalFree(hdib);

savebitmaptofile_cleanup_generic:
    return return_code;
}

/* Takes the screenshot of a window and saves it to a file. If only_client_area
   is true, then only the client area of the window is considered */
static int
ScreenshotWindow(HWND hwnd, char* filename, SDL_bool only_client_area)
{
    int width, height;
    RECT dimensions;
    HDC windowdc, capturedc;
    HBITMAP capturebitmap;
    HGDIOBJ select_success;
    BOOL blt_success;
    int return_code = 1;

    if(!filename)
    {
        SDLTest_LogError("filename argument cannot be NULL");
        return_code = 0;
        goto screenshotwindow_cleanup_generic;
    }
    if(!hwnd)
    {
        SDLTest_LogError("hwnd argument cannot be NULL");
        return_code = 0;
        goto screenshotwindow_cleanup_generic;
    }

    if(!GetWindowRect(hwnd, &dimensions))
    {
        LogLastError("GetWindowRect() failed");
        return_code = 0;
        goto screenshotwindow_cleanup_generic;
    }

    if(only_client_area)
    {
        RECT crect;
        if(!GetClientRect(hwnd, &crect))
        {
            SDLTest_LogError("GetClientRect() failed");
            return_code = 0;
            goto screenshotwindow_cleanup_generic;
        }

        width = crect.right;
        height = crect.bottom;
        windowdc = GetDC(hwnd);
        if(!windowdc)
        {
            SDLTest_LogError("GetDC() failed");
            return_code = 0;
            goto screenshotwindow_cleanup_generic;
        }
    }
    else
    {
        width = dimensions.right - dimensions.left;
        height = dimensions.bottom - dimensions.top;
        windowdc = GetWindowDC(hwnd);
        if(!windowdc)
        {
            SDLTest_LogError("GetWindowDC() failed");
            return_code = 0;
            goto screenshotwindow_cleanup_generic;
        }
    }
    
    capturedc = CreateCompatibleDC(windowdc);
    if(!capturedc)
    {
        SDLTest_LogError("CreateCompatibleDC() failed");
        return_code = 0;
        goto screenshotwindow_cleanup_windowdc;
    }
    capturebitmap = CreateCompatibleBitmap(windowdc, width, height);
    if(!capturebitmap)
    {
        SDLTest_LogError("CreateCompatibleBitmap() failed");
        return_code = 0;
        goto screenshotwindow_cleanup_capturedc;
    }
    select_success = SelectObject(capturedc, capturebitmap);
    if(!select_success || select_success == HGDI_ERROR)
    {
        SDLTest_LogError("SelectObject() failed");
        return_code = 0;
        goto screenshotwindow_cleanup_capturebitmap;
    }
    blt_success = BitBlt(capturedc, 0, 0, width, height, windowdc,
                         0, 0, SRCCOPY|CAPTUREBLT);
    if(!blt_success)
    {
        LogLastError("BitBlt() failed");
        return_code = 0;
        goto screenshotwindow_cleanup_capturebitmap;
    }

    /* save bitmap as file */
    if(!SaveBitmapToFile(windowdc, capturebitmap, filename))
    {
        SDLTest_LogError("SaveBitmapToFile() failed");
        return_code = 0;
        goto screenshotwindow_cleanup_capturebitmap;
    }

    /* Free resources */

screenshotwindow_cleanup_capturebitmap:
    if(!DeleteObject(capturebitmap))
    {
        SDLTest_LogError("DeleteObjectFailed");
        return_code = 0;
    }

screenshotwindow_cleanup_capturedc:
    if(!DeleteDC(capturedc))
    {
        SDLTest_LogError("DeleteDC() failed");
        return_code = 0;
    }

screenshotwindow_cleanup_windowdc:
    if(!ReleaseDC(hwnd, windowdc))
    {
        SDLTest_LogError("ReleaseDC() failed");
        return_code = 0;
    }

screenshotwindow_cleanup_generic:
    return return_code;
}

/* Takes the screenshot of the entire desktop and saves it to a file */
int SDLVisualTest_ScreenshotDesktop(char* filename)
{
    HWND hwnd;
    hwnd = GetDesktopWindow();
    return ScreenshotWindow(hwnd, filename, SDL_FALSE);
}

/* take screenshot of a window and save it to a file */
static BOOL CALLBACK
ScreenshotHwnd(HWND hwnd, LPARAM lparam)
{
    int len;
    DWORD pid;
    char* prefix;
    char* filename;

    GetWindowThreadProcessId(hwnd, &pid);
    if(pid != screenshot_pinfo.pi.dwProcessId)
        return TRUE;

    if(!IsWindowVisible(hwnd))
        return TRUE;

    prefix = (char*)lparam;
    len = SDL_strlen(prefix) + 100;
    filename = (char*)SDL_malloc(len * sizeof(char));
    if(!filename)
    {
        SDLTest_LogError("SDL_malloc() failed");
        return FALSE;
    }

    /* restore the window and bring it to the top */
    ShowWindowAsync(hwnd, SW_RESTORE);
    /* restore is not instantaneous */
    SDL_Delay(500);

    /* take a screenshot of the client area */
    if(img_num == 1)
        SDL_snprintf(filename, len, "%s.bmp", prefix);
    else
        SDL_snprintf(filename, len, "%s_%d.bmp", prefix, img_num);
    img_num++;
    ScreenshotWindow(hwnd, filename, SDL_TRUE);

    SDL_free(filename);
    return TRUE;
}


/* each window of the process will have a screenshot taken. The file name will be
   prefix-i.png for the i'th window. */
int
SDLVisualTest_ScreenshotProcess(SDL_ProcessInfo* pinfo, char* prefix)
{
    if(!pinfo)
    {
        SDLTest_LogError("pinfo argument cannot be NULL");
        return 0;
    }
    if(!prefix)
    {
        SDLTest_LogError("prefix argument cannot be NULL");
        return 0;
    }

    img_num = 1;
    screenshot_pinfo = *pinfo;
    if(!EnumWindows(ScreenshotHwnd, (LPARAM)prefix))
    {
        SDLTest_LogError("EnumWindows() failed");
        return 0;
    }

    return 1;
}

#endif
