/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Console Driver DLL
 * FILE:            win32ss/user/winsrv/consrv/condrv/graphics.c
 * PURPOSE:         Console Output Functions for graphics-mode screen-buffers
 * PROGRAMMERS:     Hermes Belusca-Maito (hermes.belusca@sfr.fr)
 *
 * NOTE:            See http://blog.airesoft.co.uk/2012/10/things-ms-can-do-that-they-dont-tell-you-about-console-graphics/
 *                  for more information.
 */

/* INCLUDES *******************************************************************/

#include "consrv.h"
#include "include/conio.h"
#include "include/term.h"
#include "conoutput.h"
#include "handle.h"

#define NDEBUG
#include <debug.h>


/* PRIVATE FUNCTIONS **********************************************************/

CONSOLE_IO_OBJECT_TYPE
GRAPHICS_BUFFER_GetType(PCONSOLE_SCREEN_BUFFER This)
{
    // return This->Header.Type;
    return GRAPHICS_BUFFER;
}

static CONSOLE_SCREEN_BUFFER_VTBL GraphicsVtbl =
{
    GRAPHICS_BUFFER_GetType,
};


NTSTATUS
CONSOLE_SCREEN_BUFFER_Initialize(OUT PCONSOLE_SCREEN_BUFFER* Buffer,
                                 IN OUT PCONSOLE Console,
                                 IN SIZE_T Size);
VOID
CONSOLE_SCREEN_BUFFER_Destroy(IN OUT PCONSOLE_SCREEN_BUFFER Buffer);


NTSTATUS
GRAPHICS_BUFFER_Initialize(OUT PCONSOLE_SCREEN_BUFFER* Buffer,
                           IN OUT PCONSOLE Console,
                           IN PGRAPHICS_BUFFER_INFO GraphicsInfo)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PGRAPHICS_SCREEN_BUFFER NewBuffer = NULL;

    LARGE_INTEGER SectionSize;
    ULONG ViewSize = 0;
    HANDLE ProcessHandle;

    if (Buffer == NULL || Console == NULL || GraphicsInfo == NULL)
        return STATUS_INVALID_PARAMETER;

    *Buffer = NULL;

    Status = CONSOLE_SCREEN_BUFFER_Initialize((PCONSOLE_SCREEN_BUFFER*)&NewBuffer,
                                              Console,
                                              sizeof(GRAPHICS_SCREEN_BUFFER));
    if (!NT_SUCCESS(Status)) return Status;
    NewBuffer->Header.Type = GRAPHICS_BUFFER;
    NewBuffer->Vtbl = &GraphicsVtbl;

    /*
     * Remember the handle to the process so that we can close or unmap
     * correctly the allocated resources when the client releases the
     * screen buffer.
     */
    ProcessHandle = CsrGetClientThread()->Process->ProcessHandle;
    NewBuffer->ClientProcess = ProcessHandle;

    /* Get infos from the graphics buffer information structure */
    NewBuffer->BitMapInfoLength = GraphicsInfo->Info.dwBitMapInfoLength;

    NewBuffer->BitMapInfo = ConsoleAllocHeap(HEAP_ZERO_MEMORY, NewBuffer->BitMapInfoLength);
    if (NewBuffer->BitMapInfo == NULL)
    {
        CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Adjust the bitmap height if needed (bottom-top vs. top-bottom). Use always bottom-up. */
    if (GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biHeight > 0)
        GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biHeight = -GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biHeight;

    /* We do not use anything else than uncompressed bitmaps */
    if (GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biCompression != BI_RGB)
    {
        DPRINT1("biCompression == %d != BI_RGB, correct that!\n", GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biCompression);
        GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biCompression = BI_RGB;
    }

    RtlCopyMemory(NewBuffer->BitMapInfo,
                  GraphicsInfo->Info.lpBitMapInfo,
                  GraphicsInfo->Info.dwBitMapInfoLength);

    NewBuffer->BitMapUsage = GraphicsInfo->Info.dwUsage;

    /* Set the screen buffer size. Fight against overflows. */
    if ( GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biWidth  <= 0xFFFF &&
        -GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biHeight <= 0xFFFF )
    {
        /* Be careful about the sign of biHeight */
        NewBuffer->ScreenBufferSize.X =  (SHORT)GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biWidth ;
        NewBuffer->ScreenBufferSize.Y = (SHORT)-GraphicsInfo->Info.lpBitMapInfo->bmiHeader.biHeight;

        NewBuffer->OldViewSize = NewBuffer->ViewSize = 
            NewBuffer->OldScreenBufferSize = NewBuffer->ScreenBufferSize;
    }
    else
    {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        ConsoleFreeHeap(NewBuffer->BitMapInfo);
        CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer);
        goto Quit;
    }

    /*
     * Create a mutex to synchronize bitmap memory access
     * between ourselves and the client.
     */
    Status = NtCreateMutant(&NewBuffer->Mutex, MUTANT_ALL_ACCESS, NULL, FALSE);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("NtCreateMutant() failed: %lu\n", Status);
        ConsoleFreeHeap(NewBuffer->BitMapInfo);
        CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer);
        goto Quit;
    }

    /*
     * Duplicate the Mutex for the client. We must keep a trace of it
     * so that we can close it when the client releases the screen buffer.
     */
    Status = NtDuplicateObject(NtCurrentProcess(),
                               NewBuffer->Mutex,
                               ProcessHandle,
                               &NewBuffer->ClientMutex,
                               0, 0, DUPLICATE_SAME_ACCESS);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("NtDuplicateObject() failed: %lu\n", Status);
        NtClose(NewBuffer->Mutex);
        ConsoleFreeHeap(NewBuffer->BitMapInfo);
        CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer);
        goto Quit;
    }

    /*
     * Create a memory section for the bitmap area, to share with the client.
     */
    SectionSize.QuadPart = NewBuffer->BitMapInfo->bmiHeader.biSizeImage;
    Status = NtCreateSection(&NewBuffer->hSection,
                             SECTION_ALL_ACCESS,
                             NULL,
                             &SectionSize,
                             PAGE_READWRITE,
                             SEC_COMMIT,
                             NULL);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Error: Impossible to create a shared section ; Status = %lu\n", Status);
        NtClose(NewBuffer->ClientMutex);
        NtClose(NewBuffer->Mutex);
        ConsoleFreeHeap(NewBuffer->BitMapInfo);
        CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer);
        goto Quit;
    }

    /*
     * Create a view for our needs.
     */
    ViewSize = 0;
    NewBuffer->BitMap = NULL;
    Status = NtMapViewOfSection(NewBuffer->hSection,
                                NtCurrentProcess(),
                                (PVOID*)&NewBuffer->BitMap,
                                0,
                                0,
                                NULL,
                                &ViewSize,
                                ViewUnmap,
                                0,
                                PAGE_READWRITE);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Error: Impossible to map the shared section ; Status = %lu\n", Status);
        NtClose(NewBuffer->hSection);
        NtClose(NewBuffer->ClientMutex);
        NtClose(NewBuffer->Mutex);
        ConsoleFreeHeap(NewBuffer->BitMapInfo);
        CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer);
        goto Quit;
    }

    /*
     * Create a view for the client. We must keep a trace of it so that
     * we can unmap it when the client releases the screen buffer.
     */
    ViewSize = 0;
    NewBuffer->ClientBitMap = NULL;
    Status = NtMapViewOfSection(NewBuffer->hSection,
                                ProcessHandle,
                                (PVOID*)&NewBuffer->ClientBitMap,
                                0,
                                0,
                                NULL,
                                &ViewSize,
                                ViewUnmap,
                                0,
                                PAGE_READWRITE);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Error: Impossible to map the shared section ; Status = %lu\n", Status);
        NtUnmapViewOfSection(NtCurrentProcess(), NewBuffer->BitMap);
        NtClose(NewBuffer->hSection);
        NtClose(NewBuffer->ClientMutex);
        NtClose(NewBuffer->Mutex);
        ConsoleFreeHeap(NewBuffer->BitMapInfo);
        CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer);
        goto Quit;
    }

    NewBuffer->ViewOrigin.X = NewBuffer->ViewOrigin.Y = 0;
    NewBuffer->VirtualY = 0;

    NewBuffer->CursorBlinkOn  = FALSE;
    NewBuffer->ForceCursorOff = TRUE;
    NewBuffer->CursorInfo.bVisible = FALSE;
    NewBuffer->CursorInfo.dwSize   = 0;
    NewBuffer->CursorPosition.X = NewBuffer->CursorPosition.Y = 0;

    NewBuffer->Mode = 0;

    *Buffer = (PCONSOLE_SCREEN_BUFFER)NewBuffer;
    Status = STATUS_SUCCESS;

Quit:
    return Status;
}

VOID
GRAPHICS_BUFFER_Destroy(IN OUT PCONSOLE_SCREEN_BUFFER Buffer)
{
    PGRAPHICS_SCREEN_BUFFER Buff = (PGRAPHICS_SCREEN_BUFFER)Buffer;

    /*
     * IMPORTANT !! Reinitialize the type so that we don't enter a recursive
     * infinite loop when calling CONSOLE_SCREEN_BUFFER_Destroy.
     */
    Buffer->Header.Type = SCREEN_BUFFER;

    /*
     * Uninitialize the graphics screen buffer
     * in the reverse way we initialized it.
     */
    NtUnmapViewOfSection(Buff->ClientProcess, Buff->ClientBitMap);
    NtUnmapViewOfSection(NtCurrentProcess(), Buff->BitMap);
    NtClose(Buff->hSection);
    NtClose(Buff->ClientMutex);
    NtClose(Buff->Mutex);
    ConsoleFreeHeap(Buff->BitMapInfo);

    CONSOLE_SCREEN_BUFFER_Destroy(Buffer);
}

/* EOF */
