#ifndef __RenderDevice__
#define __RenderDevice__

#ifndef CORE_DLL 
    #ifdef CORE_BUILD
	    #define CORE_DLL __declspec(dllexport)
    #else
	    #define CORE_DLL __declspec(dllimport)
        #pragma comment(lib, "Core.lib")
    #endif
#endif

#include "types.h"
#include "Device.h"
#include "mathlib.h"
#include "SceneGraph/Node.h"
#include "SceneGraph/Group.h"
#include "SceneGraph/Camera.h"
#include "SceneGraph/Transform.h"
#include "SceneGraph/Text.h"
#include "SceneGraph/Sprite.h"
#include "SceneGraph/Geode.h" 
#include "SceneGraph/CameraManipulator.h"
#include "SceneGraph/Light.h"
#include "SceneGraph/Shader.h"
#include "SceneGraph/RenderTarget.h"
#include "SceneGraph/DepthStencilBuffer.h"
#include "MersenneTwister.h"
#include "StopWatch.h"

#include <assimp.hpp>
#include <aiMesh.h>
#include <aiMaterial.h>
#include <aiAnim.h>
#include <aiScene.h>
#include <aiPostProcess.h>

#include <windows.h>
#include <string>
#include <list>
#include <vector>

#pragma warning(push)
#pragma warning(disable:4251)


class CORE_DLL RenderDevice : public Device {
public:
    RenderDevice();

    void init(HWND hWnd, DEVMODE* dmScreenSettings);
    void resizeCanvas(DEVMODE* dmScreenSettings);

    void setSceneData(Node* node);
    void setOverlayData(Node* node);

    virtual void*  loadTextureFromMemory(byte* data, uint32 size) = 0;
    virtual void*  loadTextureFromFloatArray(unsigned int width, unsigned int height, float* data) = 0;

    virtual void** loadMeshArrayFromMemory(aiMesh** rawMeshes, uint32 meshNum) = 0;

    Camera* getCamera();
    void setCameraManipulator(CameraManipulator* cameraManipulator);

    virtual Shader* createShader(const wchar_t* fileName) = 0;
    virtual RenderTarget* createRenderTarget(uint32 width, uint32 height, uint32 sampleCount) = 0;
    virtual DepthStencilBuffer* createDepthStencilBuffer(uint32 width, uint32 height, uint32 sampleCount) = 0;

    void enableLights(bool state);
    void enableShadows(bool state);
    void enableTextures(bool state);
    void enableAo(bool state);
    void enableGi(bool state);

protected:
    class CORE_DLL PerfMonitor {
    public:
        virtual void begin() = 0;
        virtual void end() = 0;

        virtual bool isValid() = 0;
        virtual uint64 getFrequency() = 0;
    };

    class CORE_DLL PerfCounter {
    public:
        virtual void mark() = 0;
        virtual uint64 recall() = 0;
    };

    virtual PerfMonitor* createPerfMonitor() = 0;
    virtual PerfCounter* createPerfCounter() = 0;

    MTRand _rng;

    struct LightSource {
        Light* light;
        Matrix4 world;
    };

    std::list<LightSource> _lightSources;


    virtual HRESULT create()  = 0;
    virtual void    destroy() = 0;
   
    virtual void drawText(Text* text) = 0;
    virtual void drawSprite(Sprite* sprite) = 0;
    virtual void drawGeode(Geode* geode) = 0;

    virtual void clear(float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 0.0f) = 0;
    virtual void flush() = 0;

    virtual void setRenderTargets(RenderTarget** renderTargets, DepthStencilBuffer* depthStencilBuffer) = 0;
    virtual void clearRenderTargets(float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 0.0f) = 0;

    void parseGraph(Node* node, bool extractLights = true);
    void renderLightsAndAO();

    void render();

    virtual HRESULT createDeviceIndependentResources() = 0;
    virtual HRESULT createDeviceResources() = 0;
    virtual HRESULT recreateSizedResources() = 0;
    virtual HRESULT createD3DDeviceResources() = 0;

    virtual void discardDeviceResources() = 0;

    void operator()();

    HWND     _hWnd;
    DEVMODE  _dmScreenSettings;
    bool     _flagResize;

    double _time, _mpf;
    LARGE_INTEGER _freq;
    LARGE_INTEGER _cnt1, _cnt2;
    double milisecCounter;
    int frameCounter;

    Node*   _root;
    Node*   _overlay;
    Camera* _debugOverlayCamera;
    Text*   _debugInfo;

    Camera* _sceneCamera;
    Camera* _ssaoCamera;
    Camera* _lightCamera;
    Camera* _combineCamera;
    Camera* _overlayCamera;

    Camera* _downsampleCamera;
    RenderTarget* _downsampleRenderTarget;
    Shader* _downsampleShader;
    
    Camera* _verticalBlurCamera;
    RenderTarget* _verticalBlurRenderTarget;
    RenderTarget* _verticalBlurRenderTargetDownsampled;
    Shader* _verticalBlurShader;
    Shader::ScalarVariable* _verticalBlurShaderTextureHeightVariable;

    Camera* _horizontalBlurCamera;
    RenderTarget* _horizontalBlurRenderTarget;
    RenderTarget* _horizontalBlurRenderTargetDownsampled;
    Shader* _horizontalBlurShader;
    Shader::ScalarVariable* _horizontalBlurShaderTextureWidthVariable;

    Camera* _tonemapCamera;
    RenderTarget* _tonemapRenderTarget;
    Shader* _tonemapShader;
    Shader::ScalarVariable* _mipLevels;

    Camera* _brightPassCamera;
    RenderTarget* _brightPassRenderTarget;
    Shader* _brightPassShader;

    Camera* _bloomCamera;
    Shader* _bloomShader;

    Sprite* _quad;
    Texture* _noiseTexture;

    std::list<Matrix4> _transformStack;

    std::list<Matrix4> _worldMatrix;
    std::list<Matrix4> _normalMatrix;
    std::list<Shader*> _shader;

    std::list<Camera*> _activeCamera;

    int _nodeCount;

    CameraManipulator* _cameraManipulator;

    bool _enableLights;
    bool _enableShadows;
    bool _enableTextures;
    bool _enableAo;
    bool _enableGi;

    Shader::ScalarVariable* g_LightsEnabled;
    Shader::ScalarVariable* g_TexturesEnabled;
    Shader::ScalarVariable* g_AoEnabled;
    Shader::ScalarVariable* g_GiEnabled;

    StopWatch _stopWatch;

    double _gbufferFillTime;
    double _shadowMapGenTime;
    double _lightCalcTime;
    double _ssaoTime;
    double _combineTime;
    double _tonemapTime;
    double _bloomTime;
    double _overlayTime;


    RenderTarget* p0_mrt0;
    RenderTarget* p0_mrt1;
    RenderTarget* p0_mrt2;
    RenderTarget* p0_mrt3;
    DepthStencilBuffer*  p0_dsb;

    RenderTarget* p1_mrt0;
    RenderTarget* p1_mrt1;
    RenderTarget* p1_mrt2;
    
    RenderTarget* p2_mrt0;

    RenderTarget* p3_mrt0;
    RenderTarget* p3_mrt1;

    RenderTarget* _tempShadowMap;

    PerfMonitor* _perfMon;
    PerfCounter* _perfCnt1;
    PerfCounter* _perfCnt2;
    PerfCounter* _perfCnt3;
    PerfCounter* _perfCnt4;
    PerfCounter* _perfCnt5;
    PerfCounter* _perfCnt6;
    PerfCounter* _perfCnt7;
    PerfCounter* _perfCnt8;
};

#pragma warning(pop)

#endif