#include "Main.h"

//remind kayvon variance-based shadow maps benefit from antialiasing

void ShadowSceneObject::Init(GraphicsDevice &GD, const String &SimplifiedFilename, const String &FullFilename, float _Theta, float _LocalZOffset)
{
    Mesh NewSimplifiedMesh(GD);
    NewSimplifiedMesh.LoadObj(SimplifiedFilename);

    Mesh NewFullMesh(GD);
    NewFullMesh.LoadObj(FullFilename);

    NewSimplifiedMesh.ApplyMatrix(NewSimplifiedMesh.UnitSphereTransform() * Matrix4::Scaling(3.0f));
    NewSimplifiedMesh.ApplyMatrix(Matrix4::Translation(Vec3f::eZ * -NewSimplifiedMesh.BoundingBox().Min.z));

    NewFullMesh.ApplyMatrix(NewFullMesh.UnitSphereTransform() * Matrix4::Scaling(3.0f));
    NewFullMesh.ApplyMatrix(Matrix4::Translation(Vec3f::eZ * -NewFullMesh.BoundingBox().Min.z));
    
    Init(GD, NewSimplifiedMesh, NewFullMesh, _Theta, _LocalZOffset);
}

void UpdateMeshTexture(BaseMesh &M)
{
    float XFrequency = 0.5f;
    float ZFrequency = 0.5f;
    for(UINT VertexIndex = 0; VertexIndex < M.VertexCount(); VertexIndex++)
    {
        MeshVertex &Vtx = M.Vertices()[VertexIndex];
        Vtx.TexCoord.x = Vtx.Pos.x * XFrequency;
        Vtx.TexCoord.y = Vtx.Pos.z * ZFrequency;
    }
}

void ShadowSceneObject::Init(GraphicsDevice &GD, const BaseMesh &_SimplifiedMesh, const BaseMesh &_FullMesh, float _Theta, float _LocalZOffset)
{
    LocalZOffset = _LocalZOffset;
    Theta = _Theta;

    SimplifiedMesh.SetGD(GD);
    SimplifiedMesh = _SimplifiedMesh;
    SimplifiedMesh.Optimize();
    
    FullMesh.SetGD(GD);
    FullMesh = _FullMesh;
    FullMesh.Optimize();

    UpdateMeshTexture(SimplifiedMesh);
    UpdateMeshTexture(FullMesh);
}

void ShadowScene::ResetLight(const Vec3f &LightPos)
{
    _LightCameras[0].Reset(LightPos, Vec3f::eY, LightPos + Vec3f::eX);
    _LightCameras[1].Reset(LightPos, -Vec3f::eY, LightPos - Vec3f::eX);
    _LightCameras[2].Reset(LightPos, Vec3f::eZ, LightPos + Vec3f::eY);
    _LightCameras[3].Reset(LightPos, -Vec3f::eZ, LightPos - Vec3f::eY);
    _LightCameras[4].Reset(LightPos, Vec3f::eX, LightPos + Vec3f::eZ);
    _LightCameras[5].Reset(LightPos, -Vec3f::eX, LightPos - Vec3f::eZ);
}

void ShadowScene::StatusText(AppState &state)
{
    /*String PausedString = "[p] Paused";
    if(_Paused)
    {
        PausedString = "[p] Not Paused";
    }

    String VarianceString = "[v] Using Normal Shadow Maps";
    if(_UseVarianceShadowMaps)
    {
        VarianceString = "[v] Using Variance Shadow Maps";
    }

    UINT CurYOffset = 22;
    state.graphics->DrawString(PausedString, 2, CurYOffset); CurYOffset += 20;
    O.GetGraphicsDevice().DrawString(VarianceString, 2, CurYOffset); CurYOffset += 20;
    O.GetGraphicsDevice().DrawInteger("[b] Blur Passes: ", _BlurPasses, 2, CurYOffset); CurYOffset += 20;
    O.GetGraphicsDevice().DrawString(String("[m] Shadow Map Resolution: ") + String(_ShadowMapResolution) + String("x") + String(_ShadowMapResolution), 2, CurYOffset); CurYOffset += 20;*/
}

void ShadowScene::UpdateScene(AppState &state)
{
    if(!_Paused)
    {
        _ElapsedTime += state.timer.SPF();
    }

    if(state.input.KeyCheckOnce(KEY_B))
    {
        _BlurPasses = (_BlurPasses + 1) % 5;
    }
    if(state.input.KeyCheckOnce(KEY_P))
    {
        _Paused = !_Paused;
    }
    if(state.input.KeyCheckOnce(KEY_V))
    {
        _UseVarianceShadowMaps = !_UseVarianceShadowMaps;
    }
    if(state.input.KeyCheckOnce(KEY_M))
    {
        /*if(_ShadowMapResolution == 64)
        {
            _ShadowMapResolution = 128;
        }
        else if(_ShadowMapResolution == 128)
        {
            _ShadowMapResolution = 256;
        }
        else */ if(_ShadowMapResolution == 256)
        {
            _ShadowMapResolution = 512;
        }
        else if(_ShadowMapResolution == 512)
        {
            _ShadowMapResolution = 1024;
        }
        else if(_ShadowMapResolution == 1024)
        {
            _ShadowMapResolution = 256;
        }
        _ShadowMap.Init(state.graphics->CastD3D9(), _ShadowMapResolution);
    }
    if(state.input.KeyCheckOnce(KEY_SPACE))
    {
        _ViewMode = ShadowSceneViewModeType((_ViewMode + 1) % ShadowSceneViewModeCount);
        ResetLight(Vec3f(0.0f, 0.0f, 2.25f));
        /*Vec3f LightPos = Vec3f(pmrnd() * 2.0f, pmrnd() * 2.0f, 0.5f + rnd() * 2.0f);
        _LightCameras[0].Reset(LightPos, Vec3f::eY, LightPos + Vec3f::eX);
        _LightCameras[1].Reset(LightPos, -Vec3f::eY, LightPos - Vec3f::eX);
        _LightCameras[2].Reset(LightPos, Vec3f::eZ, LightPos + Vec3f::eY);
        _LightCameras[3].Reset(LightPos, -Vec3f::eZ, LightPos - Vec3f::eY);
        _LightCameras[4].Reset(LightPos, Vec3f::eX, LightPos + Vec3f::eZ);
        _LightCameras[5].Reset(LightPos, -Vec3f::eX, LightPos - Vec3f::eZ);
        _LightCameras[0].Reset(Vec3f(2.0f * pmrnd(), 2.0f * pmrnd(), 1.5f + rnd()), Vec3f::eZ, Vec3f::Origin);*/
    }
    if(_ViewMode == ShadowSceneViewModeLightRotatingAroundObjects)
    {
        float RotationTheta = _ElapsedTime * 1.0f;
        float HeightTheta = _ElapsedTime * 1.4f;
        const float LightRadius = 7.0f;
        const float MinLightHeight = 2.5f;
        const float MaxLightHeight = 5.0f;
        Vec3f LightPos = Vec3f(cosf(RotationTheta) * LightRadius, sinf(RotationTheta) * LightRadius, (cosf(HeightTheta) * 0.5f + 0.5f) * (MaxLightHeight - MinLightHeight) + MinLightHeight);
        ResetLight(LightPos);
    }
}

void ShadowScene::Init(D3D9GraphicsDevice &GD)
{
    _Paused = false;
    _BlurPasses = 1;
    _ShadowMapResolution = 512;
    _UseVarianceShadowMaps = true;

    _Timer.Start();
    _ElapsedTime = 0.0f;
    _LightPerspective = Matrix4::PerspectiveFov(110.0f * Math::PIf / 180.0f, 1.0f, 0.1f, 100.0f);
    _ViewMode = ShadowSceneViewModeLightRotatingAroundObjects;

    ResetLight(Vec3f(0.0f, 0.0f, 2.25f));
    
    _Objects[0].Init(GD, "C:\\Data\\Models\\buddha10k.obj", "C:\\Data\\Models\\buddha.obj", 0.0f / 3.0f * Math::PIf * 2.0f, Math::PIf);
    _Objects[1].Init(GD, "C:\\Data\\Models\\bunny10k.obj",  "C:\\Data\\Models\\bunny.obj", 1.0f / 3.0f * Math::PIf * 2.0f, Math::PIf);
    _Objects[2].Init(GD, "C:\\Data\\Models\\dragon10k.obj", "C:\\Data\\Models\\dragon.obj", 2.0f / 3.0f * Math::PIf * 2.0f, 0.0f);
    
    //_Torus.Translate(Vec3f(0.0f, 0.0f, 0.75f));
    
    for(UINT BoxWallIndex = 0; BoxWallIndex < 6; BoxWallIndex++)
    {
        _BoxWalls[BoxWallIndex].SetGD(GD);
        _BoxTextures[BoxWallIndex].Init(GD);
        _BoxTextureRepeat[BoxWallIndex] = Vec3f(1.0f, 1.0f, 0.0f);
    }
    _WoodTexture.Init(GD);

    const float BoxSideLength = 15.0f;
    const float BoxHeight = 8.0f;
    const UINT BoxTessellation = 20;
    
    _BoxWalls[0].CreatePlane(Vec3f(-BoxSideLength, -BoxSideLength, 0.0f), Vec3f(BoxSideLength, BoxSideLength, 0.0f), BoxTessellation, BoxTessellation);
    _BoxWalls[1].CreatePlane(Vec3f(-BoxSideLength, -BoxSideLength, 0.0f), Vec3f(BoxSideLength, -BoxSideLength, BoxHeight), BoxTessellation, BoxTessellation);
    _BoxWalls[2].CreatePlane(Vec3f(-BoxSideLength, -BoxSideLength, 0.0f), Vec3f(-BoxSideLength, BoxSideLength, BoxHeight), BoxTessellation, BoxTessellation);

    _BoxWalls[3].CreatePlane(Vec3f(-BoxSideLength, -BoxSideLength, BoxHeight), Vec3f(BoxSideLength, BoxSideLength, BoxHeight), BoxTessellation, BoxTessellation);
    _BoxWalls[4].CreatePlane(Vec3f(-BoxSideLength, BoxSideLength, 0.0f), Vec3f(BoxSideLength, BoxSideLength, BoxHeight), BoxTessellation, BoxTessellation);
    _BoxWalls[5].CreatePlane(Vec3f(BoxSideLength, -BoxSideLength, 0.0f), Vec3f(BoxSideLength, BoxSideLength, BoxHeight), BoxTessellation, BoxTessellation);

    Bitmap Bmp;
    Bmp.LoadPNG("Assets\\Ceiling.png");
    _BoxTextures[3].Load(Bmp);
    Bmp.LoadPNG("Assets\\Floor.png");
    _BoxTextures[0].Load(Bmp);
    Bmp.LoadPNG("Assets\\Wall.png");
    _BoxTextures[1].Load(Bmp);
    _BoxTextures[2].Load(Bmp);
    _BoxTextures[4].Load(Bmp);
    _BoxTextures[5].Load(Bmp);
    Bmp.LoadPNG("Assets\\Wood.png");
    _WoodTexture.Load(Bmp);

    _BoxTextureRepeat[3] = Vec3f(5.0f, 5.0f, 0.0f);
    _BoxTextureRepeat[0] = Vec3f(2.0f, 2.0f, 0.0f);

    for(UINT BoxWallIndex = 0; BoxWallIndex < 6; BoxWallIndex++)
    {
        _BoxWalls[BoxWallIndex].GCNormals();
    }
}

void ShadowScene::RenderFrame(D3D9GraphicsDevice &GD, MatrixController &MC)
{
    IDirect3DDevice9 *Device = GD.GetDevice();

    MatrixController LightMC;
    LightMC.Perspective = _LightPerspective;
    for(UINT ShadowMapIndex = 0; ShadowMapIndex < ShadowMapCount; ShadowMapIndex++)
    {
        D3D9ProtectRenderTarget Protector(Device, true, true);
        _ShadowMap.SetAsRenderTarget(ShadowMapIndex, Device);
        LightMC.View = _LightCameras[ShadowMapIndex].Matrix();
        RenderScene(GD, LightMC, ShadowMapShaderCreate);
    }

    for(UINT Index = 0; Index < _BlurPasses; Index++)
    {
        _ShadowMap.BlurAllShadowMaps(Device);
    }
    
    _ShadowMap.SetAsTextures(Device);
    
    for(UINT ShadowMapIndex = 0; ShadowMapIndex < ShadowMapCount; ShadowMapIndex++)
    {
        Device->SetSamplerState(ShadowMapIndex + 1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
        Device->SetSamplerState(ShadowMapIndex + 1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
        Device->SetSamplerState(ShadowMapIndex + 1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
        Device->SetSamplerState(ShadowMapIndex + 1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
        Device->SetSamplerState(ShadowMapIndex + 1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);

        _ShadowMap.SetLightMatrices(ShadowMapIndex, _LightCameras[ShadowMapIndex], _LightPerspective);
    }

    RenderScene(GD, MC, ShadowMapShaderRender);
}

void ShadowScene::RenderScene(D3D9GraphicsDevice &GD, MatrixController &MC, ShadowMapShaderType Shader)
{
    IDirect3DDevice9 *Device = GD.GetDevice();

    Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
    Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);
    Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC);
    Device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
    Device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);

    MC.World = Matrix4::Identity();
    _ShadowMap.SetCameraMatrices(MC, Shader);

    for(UINT BoxWallIndex = 0; BoxWallIndex < 6; BoxWallIndex++)
    {
        _BoxTextures[BoxWallIndex].Set(0);

        _ShadowMap._VShaderRender.SetVec3("TextureRepeat", _BoxTextureRepeat[BoxWallIndex]);
        _ShadowMap._PShaderRender.SetFloat("LightTermMinValue", 1.0f);
        
        _BoxWalls[BoxWallIndex].Render();
    }

    Device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
    Device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);

    _WoodTexture.Set(0);
    float LocalRotationTheta = 0.0f;
    float OrbitalRotationTheta = 0.0f;
    float ObjectRadius = 0.0f;
    if(_ViewMode == ShadowSceneViewModeObjectsRotatingAroundLight)
    {
        LocalRotationTheta = _ElapsedTime * 1.647f;
        OrbitalRotationTheta = _ElapsedTime * 1.0f;
        ObjectRadius = 7.0f;
    }
    else if(_ViewMode == ShadowSceneViewModeLightRotatingAroundObjects)
    {
        LocalRotationTheta = 1.2f;
        ObjectRadius = 5.0f;
    }

    for(UINT ObjectIndex = 0; ObjectIndex < ShadowSceneObjectCount; ObjectIndex++)
    {
        const ShadowSceneObject &CurObject = _Objects[ObjectIndex];
        Matrix4 LocalRotation = Matrix4::RotationZ(LocalRotationTheta + CurObject.LocalZOffset);
        Matrix4 Translation = Matrix4::Translation(Vec3f::eX * ObjectRadius);
        Matrix4 OrbitalRotation = Matrix4::RotationZ(CurObject.Theta + OrbitalRotationTheta);
        MC.World = LocalRotation * Translation * OrbitalRotation;
        _ShadowMap.SetCameraMatrices(MC, Shader);
        _ShadowMap._VShaderRender.SetVec3("TextureRepeat", Vec3f(1.0f, 1.0f, 1.0f));
        _ShadowMap._PShaderRender.SetFloat("LightTermMinValue", 0.4f);
        if(Shader == ShadowMapShaderCreate)
        {
            CurObject.SimplifiedMesh.Render();
        }
        else if(Shader == ShadowMapShaderRender)
        {
            CurObject.FullMesh.Render();
        }
    }
}

void ShadowScene::RenderLight(D3D9GraphicsDevice &GD)
{
    
}