/**
 * Copyright 1993-2013 NVIDIA Corporation.  All rights reserved.
 *
 * Please refer to the NVIDIA end user license agreement (EULA) associated
 * with this source code for terms and conditions that govern your use of
 * this software. Any use, reproduction, disclosure, or distribution of
 * this software and related documentation outside the terms of the EULA
 * is strictly prohibited.
 *
 */

////////////////////////////////////////////////////////////////////////////////
//
//  Utility funcs to wrap up savings a surface or the back buffer as a PPM file
//  In addition, wraps up a threshold comparision of two PPMs.
//
//  These functions are designed to be used to implement an automated QA testing for SDK samples.
//
//  Author: Bryan Dudash
//  Email: sdkfeedback@nvidia.com
//
// Copyright (c) NVIDIA Corporation. All rights reserved.
////////////////////////////////////////////////////////////////////////////////

#include <helper_functions.h>
#include <rendercheck_d3d9.h>
#include <d3dx9.h>

// originally copied from checkrender_gl.cpp and slightly modified
bool CheckRenderD3D9::PPMvsPPM(const char *src_file, const char *ref_file, const char *exec_path,
                               const float epsilon, const float threshold)
{
    char *ref_file_path = sdkFindFilePath(ref_file, exec_path);

    if (ref_file_path == NULL)
    {
        printf("CheckRenderD3D9::PPMvsPPM unable to find <%s> in <%s> Aborting comparison!\n", ref_file, exec_path);
        printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file);
        printf("Aborting comparison!\n");
        printf("  FAILURE!\n");
        return false;
    }

    return (sdkComparePPM(src_file, ref_file_path, epsilon, threshold, true) == true);
};

HRESULT CheckRenderD3D9::BackbufferToPPM(IDirect3DDevice9 *pDevice, const char *zFileName)
{
    IDirect3DSurface9 *pSurface = NULL;

    if (FAILED(pDevice->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO, &pSurface)))
    {
        printf("Unable to get the back buffer.  Aborting...\n");
        return E_FAIL;
    }

    //D3DXSaveSurfaceToFile("C:\\bing.dds",D3DXIFF_DDS,pSurface,NULL,NULL);

    HRESULT hr = S_OK;
    hr = SurfaceToPPM(pDevice,pSurface,zFileName);

    pSurface->Release();

    return hr;
}

HRESULT CheckRenderD3D9::SurfaceToPPM(IDirect3DDevice9 *pDevice, IDirect3DSurface9 *pSurface, const char *zFileName)
{
    D3DSURFACE_DESC pDesc;
    pSurface->GetDesc(&pDesc);

    // $$ For now only support common 8bit formats.  TODO: support for more complex formats via conversion?
    if (!(pDesc.Format == D3DFMT_A8R8G8B8 || pDesc.Format == D3DFMT_X8R8G8B8))
    {
        return E_INVALIDARG;
    }

    IDirect3DTexture9 *pTargetTex = NULL;

    if (FAILED(pDevice->CreateTexture(pDesc.Width,pDesc.Height,1,D3DUSAGE_DYNAMIC,pDesc.Format,D3DPOOL_SYSTEMMEM,&pTargetTex,NULL)))
    {
        printf("Unable to create texture for surface transfer! Aborting...\n");
        return E_FAIL;
    }

    IDirect3DSurface9 *pTargetSurface = NULL;

    if (FAILED(pTargetTex->GetSurfaceLevel(0,&pTargetSurface)))
    {
        printf("Unable to get surface for surface transfer! Aborting...\n");
        return E_FAIL;
    }

    // This is required because we cannot lock a D3DPOOL_DEAULT surface directly.  So, we copy to our sysmem surface.
    if (FAILED(pDevice->GetRenderTargetData(pSurface,pTargetSurface)))
    {
        printf("Unable to GetRenderTargetData() for surface transfer! Aborting...\n");
        return E_FAIL;
    }

    D3DLOCKED_RECT lockedRect;
    HRESULT hr = pTargetSurface->LockRect(&lockedRect,NULL,0);

    // Need to convert from dx pitch to pitch=width
    //
    // $ PPM is BGR and not RGB it seems. Saved image looks "funny" in viewer(red and blue swapped), but since ref will be dumped using same method, this is ok.
    //      however, if we want the saved image to be properly colored, then we can swizzle the color bytes here.
    unsigned char *pPPMData = new unsigned char[pDesc.Width*pDesc.Height*4];

    for (unsigned int iHeight = 0; iHeight<pDesc.Height; iHeight++)
    {
#if 1 // swizzle to implment RGB to BGR conversion.

        for (unsigned int iWidth = 0; iWidth< pDesc.Width; iWidth++)
        {
            DWORD color = *(DWORD *)((unsigned char *)(lockedRect.pBits)+iHeight*lockedRect.Pitch + iWidth*4);

            // R<->B, [7:0] <-> [23:16], swizzle
            color = ((color&0xFF)<<16) | (color&0xFF00) | ((color&0xFF0000)>>16) | (color&0xFF000000);

            memcpy(&(pPPMData[(iHeight*pDesc.Width + iWidth)*4]),(unsigned char *)&color,4);
        }

#else
        memcpy(&(pPPMData[iHeight*pDesc.Width*4]),(unsigned char *)(lockedRect.pBits)+iHeight*lockedRect.Pitch,pDesc.Width*4);
#endif
    }

    pTargetSurface->UnlockRect();

    // Prepends the PPM header info and bumps byte data afterwards
    sdkSavePPM4ub(zFileName, pPPMData, pDesc.Width, pDesc.Height);

    delete [] pPPMData;
    pTargetSurface->Release();
    pTargetTex->Release();

    return S_OK;
}