//--------------------------------------------------------------------------------------
// File: EffectAPI.cpp
//
// Effect API entry point
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/p/?LinkId=271568
//--------------------------------------------------------------------------------------

#include "pchfx.h"

#include <memory>

using namespace D3DX11Effects;

//-------------------------------------------------------------------------------------

struct handle_closer { void operator()(HANDLE h) { if (h) CloseHandle(h); } };

typedef public std::unique_ptr<void, handle_closer> ScopedHandle;

inline HANDLE safe_handle( HANDLE h ) { return (h == INVALID_HANDLE_VALUE) ? 0 : h; }

//-------------------------------------------------------------------------------------

static HRESULT LoadBinaryFromFile( _In_z_ LPCWSTR pFileName, _Inout_ std::unique_ptr<uint8_t[]>& data, _Out_ uint32_t& size )
{
    // open the file
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    ScopedHandle hFile( safe_handle( CreateFile2( pFileName,
                                                  GENERIC_READ,
                                                  FILE_SHARE_READ,
                                                  OPEN_EXISTING,
                                                  nullptr ) ) );
#else
    ScopedHandle hFile( safe_handle( CreateFileW( pFileName,
                                                  GENERIC_READ,
                                                  FILE_SHARE_READ,
                                                  nullptr,
                                                  OPEN_EXISTING,
                                                  FILE_ATTRIBUTE_NORMAL,
                                                  nullptr ) ) );
#endif

    if ( !hFile )
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }

    // Get the file size
    LARGE_INTEGER FileSize = { 0 };

#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
    FILE_STANDARD_INFO fileInfo;
    if ( !GetFileInformationByHandleEx( hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo) ) )
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }
    FileSize = fileInfo.EndOfFile;
#else
    GetFileSizeEx( hFile.get(), &FileSize );
#endif

    // File is too big for 32-bit allocation or contains no data, so reject read
    if ( !FileSize.LowPart || FileSize.HighPart > 0)
    {
        return E_FAIL;
    }

    // create enough space for the file data
    data.reset( new uint8_t[ FileSize.LowPart ] );
    if (!data)
    {
        return E_OUTOFMEMORY;
    }

    // read the data in
    DWORD BytesRead = 0;
    if (!ReadFile( hFile.get(),
                   data.get(),
                   FileSize.LowPart,
                   &BytesRead,
                   nullptr
                 ))
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }

    if (BytesRead < FileSize.LowPart)
    {
        return E_FAIL;
    }

    size = BytesRead;

    return S_OK;
}

//--------------------------------------------------------------------------------------

_Use_decl_annotations_
HRESULT WINAPI D3DX11CreateEffectFromMemory(LPCVOID pData, SIZE_T DataLength, UINT FXFlags,
                                            ID3D11Device *pDevice, ID3DX11Effect **ppEffect, LPCSTR srcName )
{
    if ( !pData || !DataLength || !pDevice || !ppEffect )
        return E_INVALIDARG;

    if ( DataLength > UINT32_MAX )
        return E_INVALIDARG;

    HRESULT hr = S_OK;

    // Note that pData must point to a compiled effect, not HLSL
    VN( *ppEffect = new CEffect( FXFlags & D3DX11_EFFECT_RUNTIME_VALID_FLAGS) );
    VH( ((CEffect*)(*ppEffect))->LoadEffect(pData, static_cast<uint32_t>(DataLength) ) );
    VH( ((CEffect*)(*ppEffect))->BindToDevice(pDevice, (srcName) ? srcName : "D3DX11Effect" ) );

lExit:
    if (FAILED(hr))
    {
        SAFE_RELEASE(*ppEffect);
    }
    return hr;
}

//--------------------------------------------------------------------------------------

_Use_decl_annotations_
HRESULT WINAPI D3DX11CreateEffectFromFile( LPCWSTR pFileName, UINT FXFlags, ID3D11Device *pDevice, ID3DX11Effect **ppEffect )
{
    if ( !pFileName || !pDevice || !ppEffect )
        return E_INVALIDARG;

    std::unique_ptr<uint8_t[]> fileData;
    uint32_t size;
    HRESULT hr = LoadBinaryFromFile( pFileName, fileData, size );
    if ( FAILED(hr) )
        return hr;

    hr = S_OK;

    // Note that pData must point to a compiled effect, not HLSL
    VN( *ppEffect = new CEffect( FXFlags & D3DX11_EFFECT_RUNTIME_VALID_FLAGS) );
    VH( ((CEffect*)(*ppEffect))->LoadEffect( fileData.get(), size ) );

    // Create debug object name from input filename
    CHAR strFileA[MAX_PATH];
    int result = WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, pFileName, -1, strFileA, MAX_PATH, nullptr, FALSE );
    if ( !result )
    {
        DPF(0, "Failed to load effect file due to WC to MB conversion failure: %ls", pFileName);
        hr = E_FAIL;
        goto lExit;
    }

    const CHAR* pstrName = strrchr( strFileA, '\\' );
    if (!pstrName)
    {
        pstrName = strFileA;
    }
    else
    {
        pstrName++;
    }

    VH( ((CEffect*)(*ppEffect))->BindToDevice(pDevice, pstrName) );

lExit:
    if (FAILED(hr))
    {
        SAFE_RELEASE(*ppEffect);
    }
    return hr;
}


//--------------------------------------------------------------------------------------

_Use_decl_annotations_
HRESULT D3DX11CompileEffectFromMemory( LPCVOID pData, SIZE_T DataLength, LPCSTR srcName,
                                       const D3D_SHADER_MACRO *pDefines, ID3DInclude *pInclude, UINT HLSLFlags, UINT FXFlags,
                                       ID3D11Device *pDevice, ID3DX11Effect **ppEffect, ID3DBlob **ppErrors )
{
    if ( !pData || !DataLength || !pDevice || !ppEffect )
        return E_INVALIDARG;

    if ( FXFlags & D3DCOMPILE_EFFECT_CHILD_EFFECT )
    {
        DPF(0, "Effect pools (i.e. D3DCOMPILE_EFFECT_CHILD_EFFECT) not supported" );
        return E_NOTIMPL;
    }

    ID3DBlob *blob = nullptr;
    HRESULT hr = D3DCompile( pData, DataLength, srcName, pDefines, pInclude, "", "fx_5_0", HLSLFlags, FXFlags, &blob, ppErrors );
    if ( FAILED(hr) )
    {
        DPF(0, "D3DCompile of fx_5_0 profile failed: %08X", hr );
        return hr;
    }

    hr = S_OK;

    VN( *ppEffect = new CEffect( FXFlags & D3DX11_EFFECT_RUNTIME_VALID_FLAGS ) );
    VH( ((CEffect*)(*ppEffect))->LoadEffect(blob->GetBufferPointer(), static_cast<uint32_t>( blob->GetBufferSize() ) ) );
    SAFE_RELEASE( blob );

    VH( ((CEffect*)(*ppEffect))->BindToDevice(pDevice, (srcName) ? srcName : "D3DX11Effect" ) );

lExit:
    if (FAILED(hr))
    {
        SAFE_RELEASE(*ppEffect);
    }
    return hr;
}

//--------------------------------------------------------------------------------------

_Use_decl_annotations_
HRESULT D3DX11CompileEffectFromFile( LPCWSTR pFileName,
                                     const D3D_SHADER_MACRO *pDefines, ID3DInclude *pInclude, UINT HLSLFlags, UINT FXFlags,
                                     ID3D11Device *pDevice, ID3DX11Effect **ppEffect, ID3DBlob **ppErrors )
{
    if ( !pFileName || !pDevice || !ppEffect )
        return E_INVALIDARG;

    if ( FXFlags & D3DCOMPILE_EFFECT_CHILD_EFFECT )
    {
        DPF(0, "Effect pools (i.e. D3DCOMPILE_EFFECT_CHILD_EFFECT) not supported" );
        return E_NOTIMPL;
    }

    ID3DBlob *blob = nullptr;

#if (D3D_COMPILER_VERSION >= 46) && ( !defined(WINAPI_FAMILY) || ( (WINAPI_FAMILY != WINAPI_FAMILY_APP) && (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) ) )

    HRESULT hr = D3DCompileFromFile( pFileName, pDefines, pInclude, "", "fx_5_0", HLSLFlags, FXFlags, &blob, ppErrors );
    if ( FAILED(hr) )
    {
        DPF(0, "D3DCompileFromFile of fx_5_0 profile failed %08X: %ls", hr, pFileName );
        return hr;
    }

#else // D3D_COMPILER_VERSION < 46

    std::unique_ptr<uint8_t[]> fileData;
    uint32_t size;
    HRESULT hr = LoadBinaryFromFile( pFileName, fileData, size );
    if ( FAILED(hr) )
    {
        DPF(0, "Failed to load effect file %08X: %ls", hr, pFileName);
        return hr;
    }

    // Create debug object name from input filename
    CHAR strFileA[MAX_PATH];
    int result = WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, pFileName, -1, strFileA, MAX_PATH, nullptr, FALSE );
    if ( !result )
    {
        DPF(0, "Failed to load effect file due to WC to MB conversion failure: %ls", pFileName);
        return E_FAIL;
    }

    const CHAR* pstrName = strrchr( strFileA, '\\' );
    if (!pstrName)
    {
        pstrName = strFileA;
    }
    else
    {
        pstrName++;
    }

    hr = D3DCompile( fileData.get(), size, pstrName, pDefines, pInclude, "", "fx_5_0", HLSLFlags, FXFlags, &blob, ppErrors );
    if ( FAILED(hr) )
    {
        DPF(0, "D3DCompile of fx_5_0 profile failed: %08X", hr );
        return hr;
    }

#endif // D3D_COMPILER_VERSION

    if ( blob->GetBufferSize() > UINT32_MAX)
    {
        SAFE_RELEASE( blob );
        return E_FAIL;
    }

    hr = S_OK;

    VN( *ppEffect = new CEffect( FXFlags & D3DX11_EFFECT_RUNTIME_VALID_FLAGS ) );
    VH( ((CEffect*)(*ppEffect))->LoadEffect(blob->GetBufferPointer(), static_cast<uint32_t>( blob->GetBufferSize() ) ) );
    SAFE_RELEASE( blob );

#if (D3D_COMPILER_VERSION >= 46) && ( !defined(WINAPI_FAMILY) || ( (WINAPI_FAMILY != WINAPI_FAMILY_APP) && (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) ) )
    // Create debug object name from input filename
    CHAR strFileA[MAX_PATH];
    int result = WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, pFileName, -1, strFileA, MAX_PATH, nullptr, FALSE );
    if ( !result  )
    {
        DPF(0, "Failed to load effect file due to WC to MB conversion failure: %ls", pFileName);
        hr = E_FAIL;
        goto lExit;
    }

    const CHAR* pstrName = strrchr( strFileA, '\\' );
    if (!pstrName)
    {
        pstrName = strFileA;
    }
    else
    {
        pstrName++;
    }
#endif

    VH( ((CEffect*)(*ppEffect))->BindToDevice(pDevice, pstrName) );

lExit:
    if (FAILED(hr))
    {
        SAFE_RELEASE(*ppEffect);
    }
    return hr;
}
