#include "SM_Engine3DPCH.h"
#include "MBStaticList.h"
#include "SM_ParticleSystem.h"
#include "SM_KeyFrameSequence.h"
#include "SM_D3DMesh.h"
#include "MFastFPU.h"




RenderParticleSystem::RenderParticleSystem()
{
    m_iShader=-1;
}


RenderParticleSystem::~RenderParticleSystem()
{
    Shutdown();
}


#define CHUNKSIZE 1000
static bool bIndices = false;
unsigned short usIndices[CHUNKSIZE*6];



void RenderParticleSystem::SetVelocity           (Vector3D& v3dVelocity)
{
  m_v3dVelocity = v3dVelocity;
}

void RenderParticleSystem::SetAcceleration       (Vector3D& v3dAccel)
{
  m_v3dAccel = v3dAccel;
}

void RenderParticleSystem::SetDiscRadius(float fDiscRadius)
{
  m_fDiscRadius = fDiscRadius;
}

void RenderParticleSystem::SetLifetime           (float fLifetime)
{
  m_fLifetime = fLifetime;
}

void RenderParticleSystem::SetParticleSize      (float fSize)
{
  m_fSize = fSize;
}


void RenderParticleSystem::SetParticlesPerSecond (float fParticlesSecond)
{
  if (!fParticlesSecond) 
  {
    m_fPeriod = 1.E6;
  }
  else
  {
    m_fPeriod = 1.0f/fParticlesSecond;
  }
}

void RenderParticleSystem::SetParams(const char* pcParams)
{
  Vector3D v3dVelocity;
  Vector3D v3dAccel;
  float    fLifetime;
  float    fParticlesSecond;
  float    fSize;
  float    fRadius = 0.0f;

  // Hack
  if (sscanf(pcParams, "%f %f %f %f %f %f %f %f %f %f",
   &fSize,
   &fLifetime,
   &fParticlesSecond,
   &v3dVelocity.x, &v3dVelocity.y, &v3dVelocity.z, 
   &v3dAccel.x, &v3dAccel.y, &v3dAccel.z, &fRadius) !=  10)
  {
    if (sscanf(pcParams, "%f %f %f %f %f %f %f %f %f",
     &fSize,
     &fLifetime,
     &fParticlesSecond,
     &v3dVelocity.x, &v3dVelocity.y, &v3dVelocity.z, 
     &v3dAccel.x, &v3dAccel.y, &v3dAccel.z) != 9)
    {
  
    
      return;
    }    
  }

  SetParticleSize(fSize);
  SetLifetime(fLifetime);
  SetParticlesPerSecond(fParticlesSecond);
  SetVelocity(v3dVelocity);
  SetAcceleration(v3dAccel);
  SetDiscRadius(fRadius);
}

int RenderParticleSystem::Init(int iShader, D3DMesh* pAnimable)
{
    ParticleList.Init();

    m_iShader;
    Reset();
   
    m_iShader     = iShader;
    m_pAnimable   = pAnimable;
    
    

    if (!bIndices)
    {
      bIndices = true;

      unsigned i;

      for (i = 0 ; i < CHUNKSIZE ; i++)
      {
        usIndices[i*6+0]=i*4+0;
        usIndices[i*6+1]=i*4+1;
        usIndices[i*6+2]=i*4+2;

        usIndices[i*6+3]=i*4+0;
        usIndices[i*6+4]=i*4+3;
        usIndices[i*6+5]=i*4+2;          
      }
    }
    
    return 0;
}

int RenderParticleSystem::Shutdown()
{
    m_iShader=-1;
    return 0;
}

int RenderParticleSystem::Reset()
{
    ParticleList.Init();
    m_v3dVelocity = Vector3D(0, 10.0, 0);
    m_v3dAccel    = Vector3D(0, 0.0, 0);
    m_fLastFrame  = 0.0f;
    m_fPeriod     = 1.0f/50.0f;
    m_fLifetime   = 3.0;
    m_fSize       = 1.0f;
    m_fFadeOffset = 0.8f;
    m_fDiscRadius = 10.0f;

    return 0;
}


static float fOld = 0.0f;
      

void RenderParticleSystem::Render(RenderContext* pRenderContext, int iOutcode, float fAnimableTime)
{
    ResourceManager::NewFrame();
    SM_D3d::SetRenderState(D3DRS_LIGHTING, FALSE);  
    SM_D3d::Device()->SetVertexShader(FVF_POSNORMALDIFFUSETEX1);    
    SM_D3d::SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
    SM_D3d::SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
    SM_D3d::SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
    SM_D3d::SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);  
    SM_D3d::SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_POINT);
    SM_D3d::SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);                
    SM_D3d::SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
    SM_D3d::SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);  
    SM_D3d::SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
    SM_D3d::SetRenderState(D3DRS_ZFUNC,    D3DCMP_ALWAYS);
    SM_D3d::SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
    SM_D3d::Device()->SetTransform(D3DTS_WORLD, (D3DMATRIX*) &Matrix4X4::Identity);

     
    float fTime=Timer::GetTime();

    
    float fStartAnimTime = fAnimableTime - (fTime - m_fLastFrame);
    Vector3D vOrigin;
    Quaternion q;
      
      
    for ( ; m_fLastFrame < fTime ; m_fLastFrame += m_fPeriod, fStartAnimTime += m_fPeriod)
    {
      
      if (m_pAnimable->m_pKeyFrameSequence)
      {
        m_pAnimable->m_pKeyFrameSequence->GetKeyFrame(fStartAnimTime, 
                                                      m_pAnimable->m_fKeyFrameSequenceStartTime,
                                                      &vOrigin,
                                                      &q);
      }
      else
      {
        vOrigin = m_v3dPosition;
        q       = m_qRotation;
      }
      
      Vector3D v3dVel = Vector3D( ((rand()*(1.0f/float(RAND_MAX)))-0.5f)*m_v3dVelocity.y*m_v3dVelocity.z,                                    
                                  m_v3dVelocity.z,
                                  ((rand()*(1.0f/float(RAND_MAX)))-0.5f)*m_v3dVelocity.x*m_v3dVelocity.z);

      Vector3D v3dVelocity = q*v3dVel;


      Particle p;

      Vector3D v3dRight = q*Vector3D(1.0f, 0.0f, 0.0f);
      Vector3D v3dUp = q*Vector3D(0.0f, 1.0f, 0.0f);
      
      float fDisX = ((rand()*(1.0f/float(RAND_MAX)))-0.5f)*m_fDiscRadius;
      float fDisY = ((rand()*(1.0f/float(RAND_MAX)))-0.5f)*m_fDiscRadius;


      p.v3dStart   =vOrigin+v3dRight*fDisX+v3dUp*fDisY;
      p.v3dVelocity=v3dVelocity;
      

      p.v3dAccel   =m_v3dAccel;
      p.fDeath     =m_fLastFrame+m_fLifetime;
      p.fBirth     =m_fLastFrame;

      if (p.fDeath >= fTime)
      {
        ParticleList.InsertTail(p);
      }                  
    }
        
    

    if (m_iShader == -1)
    {
      ShaderManager::GetShader("NULL")->SetShaderState(0);
    }
    else
    {
      ShaderManager::GetShader(m_iShader)->SetShaderState(0);
    } 
    

    unsigned uParticles=ParticleList.GetNumberElements();

    Matrix4X4 m;
    pRenderContext->GetViewport()->ViewMatrix(m);

    //Vector3D vr= Vector3D(m.m_11, m.m_21, m.m_31)*m_fSize*0.5f;
    //Vector3D vu= Vector3D(m.m_12, m.m_22, m.m_32)*m_fSize*0.5f;

    Vector3D vr =pRenderContext->m_VRP*m_fSize*0.5f;
    Vector3D vu =pRenderContext->m_VUP*m_fSize*0.5f;

    unsigned i,j=0;
    int iNext;
    unsigned uLive;

    int iIterator;
    for (iIterator=ParticleList.First() ; iIterator!=-1 ; )
    {
      
      FVF_PosNormalDiffuseTex1* pVertex;
      unsigned short*           pusIndices;
      int                       iVertexOffset, iIndexOffset;        

      // Allocate VB and IB
      if ((iVertexOffset=ResourceManager::GiveVBChunk(ResourceManager::m_iPosNormalDiffuseTex1Stream, 4*CHUNKSIZE*sizeof(FVF_PosNormalDiffuseTex1), (void**)&pVertex))==-1)
      {
        assert(0);
        continue;
      }

      if ((iIndexOffset=ResourceManager::GiveIBChunk(ResourceManager::m_iIndexStream, 6*CHUNKSIZE*sizeof(unsigned short), (void**)&pusIndices))==-1)
      {
        assert(0);
        continue;
      }
      
      
      uLive=0;
      for (i=0 ; i<CHUNKSIZE ; i++)
      {
        iNext=ParticleList.Next(iIterator);  
        
        Particle* pParticle;
        ParticleList.Get(iIterator, pParticle);

        if (fTime >= pParticle->fDeath) 
        {
          ParticleList.Delete(iIterator);
        }
        else
        {
          float x,y,z;

          float fLive=fTime-pParticle->fBirth;

          unsigned diffuse;
          float fFadeLimit = (pParticle->fDeath - pParticle->fBirth)*m_fFadeOffset;
          if (fLive > fFadeLimit)
          {

            float fFade = 255.0f * (1.0f - (fLive - fFadeLimit) / ((1.0f - m_fFadeOffset)*(pParticle->fDeath - pParticle->fBirth)));

            diffuse = MFPU_ftolFast(fFade);
            diffuse |= (diffuse << 24) |
                       (diffuse << 16) |
                       (diffuse <<  8);            
          }
          else
          {
            diffuse = 0xFFFFFFFF;
          }

          x=pParticle->v3dStart.x+fLive*pParticle->v3dVelocity.x+fLive*fLive*pParticle->v3dAccel.x;
          y=pParticle->v3dStart.y+fLive*pParticle->v3dVelocity.y+fLive*fLive*pParticle->v3dAccel.y;
          z=pParticle->v3dStart.z+fLive*pParticle->v3dVelocity.z+fLive*fLive*pParticle->v3dAccel.z;
                    
          pVertex[0].x=x-vr.x-vu.x;     pVertex[0].y=y-vr.y-vu.y; pVertex[0].z=z-vr.z-vu.z;
          pVertex[0].diffuse=diffuse; pVertex[0].u=0.0f;  pVertex[0].v=0.0f, 

          pVertex[1].x=x+vr.x-vu.x;     pVertex[1].y=y+vr.y-vu.y; pVertex[1].z=z+vr.z-vu.z;
          pVertex[1].diffuse=diffuse; pVertex[1].u=1.0f;  pVertex[1].v=0.0f, 

          pVertex[2].x=x+vr.x+vu.x;     pVertex[2].y=y+vr.y+vu.y; pVertex[2].z=z+vr.z+vu.z;
          pVertex[2].diffuse=diffuse; pVertex[2].u=1.0f;  pVertex[2].v=1.0f, 

          pVertex[3].x=x-vr.x+vu.x;     pVertex[3].y=y-vr.y+vu.y; pVertex[3].z=z-vr.z+vu.z;
          pVertex[3].diffuse=diffuse; pVertex[3].u=0.0f;  pVertex[3].v=1.0f, 

      
          pVertex+=4;
          
          uLive++;          
        }
        
        iIterator=iNext;

        if (iIterator == -1)
        {
          break;
        }
      }

      memcpy(pusIndices, usIndices, sizeof(unsigned short)*6*uLive);

      ResourceManager::DoneVBChunk(ResourceManager::m_iPosNormalDiffuseTex1Stream);
      ResourceManager::DoneIBChunk(ResourceManager::m_iIndexStream);
        
      if (uLive)
      {
        unsigned uStartIndex =iIndexOffset/sizeof(unsigned short);
        unsigned uStartVertex=iVertexOffset/sizeof(FVF_PosNormalDiffuseTex1);
 
        SM_D3d::Device()->SetStreamSource(
            0, 
            ResourceManager::GetVertexBufferFromID(ResourceManager::m_iPosNormalDiffuseTex1Stream), 
            sizeof(FVF_PosNormalDiffuseTex1));  
        
        SM_D3d::Device()->SetIndices(
        ResourceManager::GetIndexBufferFromID(ResourceManager::m_iIndexStream), uStartVertex);        
  
        SM_D3d::Device()->DrawIndexedPrimitive(
          D3DPT_TRIANGLELIST, 
          0,
          uLive*4,
          uStartIndex,
          min(0xFFFFF, uLive*2));   
          
        ShaderManager::AddPolygons(uLive*2);
      }

      j+=CHUNKSIZE;
    }    
}