//--------------------------------------------------------------------------------------
// File: Model.cpp
//
// 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/?LinkId=248929
//--------------------------------------------------------------------------------------

#include "pch.h"
#include "Model.h"

#include "CommonStates.h"
#include "Effects.h"
#include "PlatformHelpers.h"

using namespace DirectX;

#ifndef _CPPRTTI 
#error Model requires RTTI
#endif

//--------------------------------------------------------------------------------------
// ModelMeshPart
//--------------------------------------------------------------------------------------

ModelMeshPart::ModelMeshPart() :
    indexCount(0),
    startIndex(0),
    vertexOffset(0),
    vertexStride(0),
    primitiveType(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST),
    indexFormat(DXGI_FORMAT_R16_UINT),
    isAlpha(false)
{
}


_Use_decl_annotations_
void ModelMeshPart::Draw( ID3D11DeviceContext* deviceContext, IEffect* effect, ID3D11InputLayout* inputLayout, std::function<void()> setCustomState ) const
{
    deviceContext->IASetInputLayout( inputLayout );

    auto vb = vertexBuffer.Get();
    UINT vbStride = vertexStride;
    UINT vbOffset = 0;
    deviceContext->IASetVertexBuffers( 0, 1, &vb, &vbStride, &vbOffset );

    // Note that if indexFormat is DXGI_FORMAT_R32_UINT, this model mesh part requires a Feature Level 9.2 or greater device
    deviceContext->IASetIndexBuffer( indexBuffer.Get(), indexFormat, 0 );

    assert( effect != 0 );
    effect->Apply( deviceContext );

    // Hook lets the caller replace our shaders or state settings with whatever else they see fit.
    if ( setCustomState )
    {
        setCustomState();
    }

    // Draw the primitive.
    deviceContext->IASetPrimitiveTopology( primitiveType );

    deviceContext->DrawIndexed( indexCount, startIndex, vertexOffset );
}


_Use_decl_annotations_
void ModelMeshPart::CreateInputLayout( ID3D11Device* d3dDevice, IEffect* effect, ID3D11InputLayout** inputLayout )
{
    if ( !vbDecl || vbDecl->empty() )
        throw std::exception("Model mesh part missing vertex buffer input elements data");

    void const* shaderByteCode;
    size_t byteCodeLength;

    assert( effect != 0 );
    effect->GetVertexShaderBytecode(&shaderByteCode, &byteCodeLength);

    assert( d3dDevice != 0 );

    ThrowIfFailed(
        d3dDevice->CreateInputLayout(&(vbDecl->front()),
                                     static_cast<UINT>( vbDecl->size() ),
                                     shaderByteCode, byteCodeLength,
                                     inputLayout )
    );
}


_Use_decl_annotations_
void ModelMeshPart::ModifyEffect( ID3D11Device* d3dDevice, std::shared_ptr<IEffect>& effect, bool isalpha )
{
    if ( !vbDecl || vbDecl->empty() )
        throw std::exception("Model mesh part missing vertex buffer input elements data");

    assert( effect != 0 );
    this->effect = effect;
    this->isAlpha = isalpha;

    void const* shaderByteCode;
    size_t byteCodeLength;

    effect->GetVertexShaderBytecode(&shaderByteCode, &byteCodeLength);

    assert( d3dDevice != 0 );

    ThrowIfFailed(
        d3dDevice->CreateInputLayout(&(vbDecl->front()),
                                        static_cast<UINT>( vbDecl->size() ),
                                        shaderByteCode, byteCodeLength,
                                        &inputLayout )
    );
}


//--------------------------------------------------------------------------------------
// ModelMesh
//--------------------------------------------------------------------------------------

ModelMesh::ModelMesh() :
    ccw(true),
    pmalpha(true)
{
}


_Use_decl_annotations_
void ModelMesh::PrepareForRendering( ID3D11DeviceContext* deviceContext, CommonStates& states, bool alpha, bool wireframe ) const
{
    assert( deviceContext != 0 );

    // Set the blend and depth stencil state.
    ID3D11BlendState* blendState;
    ID3D11DepthStencilState* depthStencilState;

    if ( alpha )
    {
        if ( pmalpha )
        {
            blendState = states.AlphaBlend();
            depthStencilState = states.DepthRead();
        }
        else
        {
            blendState = states.NonPremultiplied();
            depthStencilState = states.DepthRead();
        }
    }
    else
    {
        blendState = states.Opaque();
        depthStencilState = states.DepthDefault();
    }

    deviceContext->OMSetBlendState(blendState, nullptr, 0xFFFFFFFF);
    deviceContext->OMSetDepthStencilState(depthStencilState, 0);

    // Set the rasterizer state.
    if ( wireframe )
        deviceContext->RSSetState( states.Wireframe() );
    else
        deviceContext->RSSetState( ccw ? states.CullCounterClockwise() : states.CullClockwise() );

    // Set sampler state.
    ID3D11SamplerState* samplers[] =
    {
        states.LinearWrap(),
    };

    deviceContext->PSSetSamplers( 0, 1, samplers );
}


_Use_decl_annotations_
void ModelMesh::Draw( ID3D11DeviceContext* deviceContext,
                      CXMMATRIX world, CXMMATRIX view, CXMMATRIX projection,
                      bool alpha,
                      std::function<void()> setCustomState ) const
{
    assert( deviceContext != 0 );

    for ( auto it = meshParts.cbegin(); it != meshParts.cend(); ++it )
    {
        auto part = (*it).get();
        assert( part != 0 );

        if ( part->isAlpha != alpha )
        {
            // Skip alpha parts when drawing opaque or skip opaque parts if drawing alpha
            continue;
        }

        auto imatrices = dynamic_cast<IEffectMatrices*>( part->effect.get() );
        if ( imatrices )
        {
            imatrices->SetWorld( world );
            imatrices->SetView( view );
            imatrices->SetProjection( projection );
        }

        part->Draw( deviceContext, part->effect.get(), part->inputLayout.Get(), setCustomState );
    }
}


//--------------------------------------------------------------------------------------
// Model
//--------------------------------------------------------------------------------------

_Use_decl_annotations_
void Model::Draw( ID3D11DeviceContext* deviceContext, CommonStates& states,
                  CXMMATRIX world, CXMMATRIX view, CXMMATRIX projection,
                  bool wireframe, std::function<void()> setCustomState ) const
{
    assert( deviceContext != 0 );

    // Draw opaque parts
    for( auto it = meshes.cbegin(); it != meshes.cend(); ++it )
    {
        auto mesh = it->get();
        assert( mesh != 0 );

        mesh->PrepareForRendering( deviceContext, states, false, wireframe );

        mesh->Draw( deviceContext, world, view, projection, false, setCustomState );
    }

    // Draw alpha parts
    for( auto it = meshes.cbegin(); it != meshes.cend(); ++it )
    {
        auto mesh = it->get();
        assert( mesh != 0 );

        mesh->PrepareForRendering( deviceContext, states, true, wireframe );

        mesh->Draw( deviceContext, world, view, projection, true, setCustomState );
    }
}


void Model::UpdateEffects( _In_ std::function<void(IEffect*)> setEffect )
{
    if ( mEffectCache.empty() )
    {
        // This cache ensures we only set each effect once (could be shared)
        for( auto mit  = meshes.cbegin(); mit != meshes.cend(); ++mit )
        {
            auto mesh = mit->get();
            assert( mesh != 0 );

            for ( auto it = mesh->meshParts.cbegin(); it != mesh->meshParts.cend(); ++it )
            {
                if ( (*it)->effect != 0 )
                    mEffectCache.insert( (*it)->effect.get() );
            }
        }
    }

    assert( setEffect != 0 );

    for( auto it = mEffectCache.begin(); it != mEffectCache.end(); ++it )
    {
        setEffect( *it );
    }
}
