//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "platform/platform.h"
#include "materials/matInstance.h"

#include "materials/materialManager.h"
#include "materials/customMaterialDefinition.h"
#include "materials/processedMaterial.h"
#include "materials/processedFFMaterial.h"
#include "materials/processedShaderMaterial.h"
#include "materials/processedCustomMaterial.h"
#include "materials/materialFeatureTypes.h"
#include "shaderGen/featureMgr.h"
#include "gfx/gfxDevice.h"
#include "gfx/sim/cubemapData.h"
#include "gfx/gfxCubemap.h"
#include "core/util/safeDelete.h"

class MatInstParameters;

class MatInstanceParameterHandle : public MaterialParameterHandle
{
public:
   virtual ~MatInstanceParameterHandle() {}
   MatInstanceParameterHandle(const String& name);

   void loadHandle(ProcessedMaterial* pmat);
   
   // MaterialParameterHandle interface
   const String& getName() const { return mName; }
   virtual bool isValid() const; 
   virtual S32 getSamplerRegister( U32 pass ) const;
private:   
   friend class MatInstParameters;
   String mName;
   MaterialParameterHandle* mProcessedHandle;
};

MatInstanceParameterHandle::MatInstanceParameterHandle(const String& name)
{
   mName = name;
   mProcessedHandle = NULL;
}

bool MatInstanceParameterHandle::isValid() const
{
   return mProcessedHandle && mProcessedHandle->isValid();
}

S32 MatInstanceParameterHandle::getSamplerRegister( U32 pass ) const
{ 
   if ( !mProcessedHandle )
      return -1;
   return mProcessedHandle->getSamplerRegister( pass );
}

void MatInstanceParameterHandle::loadHandle(ProcessedMaterial* pmat)                                         
{
   mProcessedHandle = pmat->getMaterialParameterHandle(mName);
}

MatInstParameters::MatInstParameters() 
{ 
   mOwnParameters = false; 
   mParameters = NULL;
}

MatInstParameters::MatInstParameters(MaterialParameters* matParams)
{
   mOwnParameters = false; 
   mParameters = matParams;
}

void MatInstParameters::loadParameters(ProcessedMaterial* pmat)
{
   mOwnParameters = true; 
   mParameters = pmat->allocMaterialParameters();
}

MatInstParameters::~MatInstParameters()
{
   if (mOwnParameters)
      SAFE_DELETE(mParameters);
}

const Vector<GFXShaderConstDesc>& MatInstParameters::getShaderConstDesc() const
{ 
   return mParameters->getShaderConstDesc(); 
}

U32 MatInstParameters::getAlignmentValue(const GFXShaderConstType constType)
{
   return mParameters->getAlignmentValue(constType);
}

#define MATINSTPARAMSET(handle, f) \
   if (!mParameters) \
      return; \
   AssertFatal(dynamic_cast<MatInstanceParameterHandle*>(handle), "Invalid handle type!"); \
   MatInstanceParameterHandle* mph = static_cast<MatInstanceParameterHandle*>(handle); \
   mParameters->set(mph->mProcessedHandle, f); \

void MatInstParameters::set(MaterialParameterHandle* handle, const F32 f)
{
   MATINSTPARAMSET(handle, f);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const Point2F& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const Point3F& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const Point4F& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const ColorF& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const S32 f)
{
   MATINSTPARAMSET(handle, f);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const Point2I& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const Point3I& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const Point4I& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<F32>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<Point2F>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<Point3F>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<Point4F>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<S32>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<Point2I>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<Point3I>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray<Point4I>& fv)
{
   MATINSTPARAMSET(handle, fv);
}

void MatInstParameters::set(MaterialParameterHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType)
{
   AssertFatal(dynamic_cast<MatInstanceParameterHandle*>(handle), "Invalid handle type!"); 
   MatInstanceParameterHandle* mph = static_cast<MatInstanceParameterHandle*>(handle); 
   mParameters->set(mph->mProcessedHandle, mat, matrixType); 
}

void MatInstParameters::set(MaterialParameterHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType)
{
   AssertFatal(dynamic_cast<MatInstanceParameterHandle*>(handle), "Invalid handle type!"); 
   MatInstanceParameterHandle* mph = static_cast<MatInstanceParameterHandle*>(handle); 
   mParameters->set(mph->mProcessedHandle, mat, arraySize, matrixType); 
}
#undef MATINSTPARAMSET

//****************************************************************************
// Material Instance
//****************************************************************************
MatInstance::MatInstance( Material &mat )
{
   VECTOR_SET_ASSOCIATION( mCurrentHandles );
   VECTOR_SET_ASSOCIATION( mCurrentParameters );

   mMaterial = &mat;

   mCreatedFromCustomMaterial = (dynamic_cast<CustomMaterial *>(&mat) != NULL);

   construct();
}

//----------------------------------------------------------------------------
// Construct
//----------------------------------------------------------------------------
void MatInstance::construct()
{
   mUserObject = NULL;
   mCurPass = -1;
   mProcessedMaterial = false;
   mVertexFormat = NULL;
   mMaxStages = 1;
   mActiveParameters = NULL;
   mDefaultParameters = NULL;
   mHasNormalMaps = false;
   mIsForwardLit = false;
   mIsValid = false;

   MATMGR->_track(this);
}

//----------------------------------------------------------------------------
// Destructor
//----------------------------------------------------------------------------
MatInstance::~MatInstance()
{
   SAFE_DELETE(mProcessedMaterial);
   SAFE_DELETE(mDefaultParameters);
   for (U32 i = 0; i < mCurrentHandles.size(); i++)
      SAFE_DELETE(mCurrentHandles[i]);   

   MATMGR->_untrack(this);
}

//----------------------------------------------------------------------------
// Init
//----------------------------------------------------------------------------
bool MatInstance::init( const FeatureSet &features, 
                        const GFXVertexFormat *vertexFormat ) 
{
   AssertFatal( vertexFormat, "MatInstance::init - Got null vertex format!" );

   mFeatureList = features;
   mVertexFormat = vertexFormat;

   SAFE_DELETE(mProcessedMaterial);   
   mIsValid = processMaterial();         

   return mIsValid;
}


//----------------------------------------------------------------------------
// reInitialize
//----------------------------------------------------------------------------
bool MatInstance::reInit()
{
   SAFE_DELETE(mProcessedMaterial);
   deleteAllHooks();
   mIsValid = processMaterial();

   if ( mIsValid )
   {
      for (U32 i = 0; i < mCurrentHandles.size(); i++)
         mCurrentHandles[i]->loadHandle(mProcessedMaterial);

      for (U32 i = 0; i < mCurrentParameters.size(); i++)
         mCurrentParameters[i]->loadParameters(mProcessedMaterial);
   }

   return mIsValid;
}

//----------------------------------------------------------------------------
// Process stages
//----------------------------------------------------------------------------
bool MatInstance::processMaterial()
{
   AssertFatal( mMaterial, "MatInstance::processMaterial - Got null material!" );
   //AssertFatal( mVertexFormat, "MatInstance::processMaterial - Got null vertex format!" );
   if ( !mMaterial || !mVertexFormat )   
      return false;   

   SAFE_DELETE(mDefaultParameters);

   CustomMaterial *custMat = NULL;

   if( dynamic_cast<CustomMaterial*>(mMaterial) )
   {
      F32 pixVersion = GFX->getPixelShaderVersion();
      custMat = static_cast<CustomMaterial*>(mMaterial);
      if ((custMat->mVersion > pixVersion) || (custMat->mVersion == 0.0))
      {
         if(custMat->mFallback)
         {
            mMaterial = custMat->mFallback;
            return processMaterial();            
         }
         else
         {            
            AssertWarn(custMat->mVersion == 0.0f, avar("Can't load CustomMaterial %s for %s, using generic FF fallback", 
               String(mMaterial->getName()).isEmpty() ? "Unknown" : mMaterial->getName(), custMat->mMapTo.c_str()));
            mProcessedMaterial = new ProcessedFFMaterial(*mMaterial);
         }
      }
      else 
         mProcessedMaterial = new ProcessedCustomMaterial(*mMaterial);
   }
   else if(GFX->getPixelShaderVersion() > 0.001)
      mProcessedMaterial = getShaderMaterial();
   else
      mProcessedMaterial = new ProcessedFFMaterial(*mMaterial);

   if (mProcessedMaterial)
   {
      mProcessedMaterial->addStateBlockDesc( mUserDefinedState );
      mProcessedMaterial->setShaderMacros( mUserMacros );
      mProcessedMaterial->setUserObject( mUserObject );

      FeatureSet features( mFeatureList );
      features.exclude( MATMGR->getExclusionFeatures() );
      
      if( !mProcessedMaterial->init(features, mVertexFormat, mFeaturesDelegate) )
      {
         Con::errorf( "Failed to initialize material '%s'", getMaterial()->getName() );
         SAFE_DELETE( mProcessedMaterial );
         return false;
      }

      mDefaultParameters = new MatInstParameters(mProcessedMaterial->getDefaultMaterialParameters());
      mActiveParameters = mDefaultParameters;

      const FeatureSet &finalFeatures = mProcessedMaterial->getFeatures();
      mHasNormalMaps = finalFeatures.hasFeature( MFT_NormalMap );

      mIsForwardLit =   (  custMat && custMat->mForwardLit ) || 
                        (  !finalFeatures.hasFeature( MFT_IsEmissive ) &&
                           finalFeatures.hasFeature( MFT_ForwardShading ) );

      return true;
   }
   
   return false;
}

const MatStateHint& MatInstance::getStateHint() const
{
   if ( mProcessedMaterial )
      return mProcessedMaterial->getStateHint();
   else
      return MatStateHint::Default;
}

ProcessedMaterial* MatInstance::getShaderMaterial()
{
   return new ProcessedShaderMaterial(*mMaterial);
}

void MatInstance::addStateBlockDesc(const GFXStateBlockDesc& desc)
{   
   mUserDefinedState = desc;
}

void MatInstance::updateStateBlocks()
{
   if ( mProcessedMaterial )
      mProcessedMaterial->updateStateBlocks();
}

void MatInstance::addShaderMacro( const String &name, const String &value )
{   
   // Check to see if we already have this macro.
   Vector<GFXShaderMacro>::iterator iter = mUserMacros.begin();
   for ( ; iter != mUserMacros.end(); iter++ )
   {
      if ( iter->name == name )
      {
         iter->value = value;
         return;
      }
   }

   // Add a new macro.
   mUserMacros.increment();
   mUserMacros.last().name = name;
   mUserMacros.last().value = value;
}

//----------------------------------------------------------------------------
// Setup pass - needs scenegraph data because the lightmap will change across
//    several materials.
//----------------------------------------------------------------------------
bool MatInstance::setupPass(SceneRenderState * state, const SceneData &sgData )
{
   PROFILE_SCOPE( MatInstance_SetupPass );
   
   if( !mProcessedMaterial )
      return false;

   ++mCurPass;

   if ( !mProcessedMaterial->setupPass( state, sgData, mCurPass ) )
   {
      mCurPass = -1;
      return false;
   }

   return true;
}

void MatInstance::setTransforms(const MatrixSet &matrixSet, SceneRenderState *state)
{
   PROFILE_SCOPE(MatInstance_setTransforms);
   mProcessedMaterial->setTransforms(matrixSet, state, getCurPass());
}

void MatInstance::setSceneInfo(SceneRenderState * state, const SceneData& sgData)
{
   PROFILE_SCOPE(MatInstance_setSceneInfo);
   mProcessedMaterial->setSceneInfo(state, sgData, getCurPass());
}

void MatInstance::setBuffers(GFXVertexBufferHandleBase* vertBuffer, GFXPrimitiveBufferHandle* primBuffer)
{
   mProcessedMaterial->setBuffers(vertBuffer, primBuffer);
}

void MatInstance::setTextureStages(SceneRenderState * state, const SceneData &sgData )
{
   PROFILE_SCOPE(MatInstance_setTextureStages);
   mProcessedMaterial->setTextureStages(state, sgData, getCurPass());
}

bool MatInstance::isInstanced() const 
{
   return mProcessedMaterial->getFeatures().hasFeature( MFT_UseInstancing );
}

bool MatInstance::stepInstance()
{
   AssertFatal( isInstanced(), "MatInstance::stepInstance - This material isn't instanced!" );
   AssertFatal( mCurPass >= 0, "MatInstance::stepInstance - Must be within material setup pass!" );

   return mProcessedMaterial->stepInstance();
}

U32 MatInstance::getCurStageNum()
{
   return mProcessedMaterial->getStageFromPass(getCurPass());
}

RenderPassData* MatInstance::getPass(U32 pass)
{
   return mProcessedMaterial->getPass(pass);
}

bool MatInstance::hasGlow() 
{ 
   if( mProcessedMaterial )
      return mProcessedMaterial->hasGlow(); 
   else
      return false;
}

const FeatureSet& MatInstance::getFeatures() const 
{
   return mProcessedMaterial->getFeatures(); 
}

MaterialParameterHandle* MatInstance::getMaterialParameterHandle(const String& name)
{
   AssertFatal(mProcessedMaterial, "Not init'ed!"); 
   for (U32 i = 0; i < mCurrentHandles.size(); i++)
   {
      if (mCurrentHandles[i]->getName().equal(name))
      {
         return mCurrentHandles[i];
      }
   }
   MatInstanceParameterHandle* mph = new MatInstanceParameterHandle(name);
   mph->loadHandle(mProcessedMaterial);
   mCurrentHandles.push_back(mph);
   return mph;
}

MaterialParameters* MatInstance::allocMaterialParameters() 
{  
   AssertFatal(mProcessedMaterial, "Not init'ed!"); 
   MatInstParameters* mip = new MatInstParameters();
   mip->loadParameters(mProcessedMaterial);
   mCurrentParameters.push_back(mip);
   return mip;   
}

void MatInstance::setMaterialParameters(MaterialParameters* param) 
{ 
   AssertFatal(mProcessedMaterial, "Not init'ed!"); 
   mProcessedMaterial->setMaterialParameters(param, mCurPass);
   AssertFatal(dynamic_cast<MatInstParameters*>(param), "Incorrect param type!");
   mActiveParameters = static_cast<MatInstParameters*>(param);
}

MaterialParameters* MatInstance::getMaterialParameters()
{ 
   AssertFatal(mProcessedMaterial, "Not init'ed!"); 
   return mActiveParameters;
}

void MatInstance::dumpShaderInfo() const
{
   if ( mMaterial == NULL )
   {
      Con::errorf( "Trying to get Material information on an invalid MatInstance" );
      return;
   }

   Con::printf( "Material Info for object %s - %s", mMaterial->getName(), mMaterial->mMapTo.c_str() );

   if ( mProcessedMaterial == NULL )
   {
      Con::printf( "  [no processed material!]" );
      return;
   }

   mProcessedMaterial->dumpMaterialInfo();
}
