//#define MAKE_VIDEO
#define ROTATE
//#define MAKE_SCREENSHOT

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using PostscreenEffects;

namespace LightShafts
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        #region Application Controls
        private Vector2             _LightMapPosition       = new Vector2( 0.7f, 0.44f );
        private float               _LightShaftExposure     = 0.15f;
        private float               _LightShaftDecay        = 0.99f;
        private float               _GearExposure           = 0.6f;
        private float               _LuminanceThreshold     = 1.0f;
        private float               _LuminanceScaleFactor   = 2.0f;
        #endregion

#if MAKE_VIDEO
        private int                 _Width = 1024;
        private int                 _Height = 768;
#else
        private int                 _Width = 1024;
        private int                 _Height = 768;
#endif
        private Effect              _EffectWorld;
        private Effect              _EffectBlack;
        private Effect              _EffectShadowMap;
        
        private Matrix              _WorldView;
        private Matrix              _WorldProjection;
        private RenderTarget2D      _RenderTargetColor;
        private RenderTarget2D      _RenderTargetTemp;
        private RenderTarget2D      _RenderTargetMask;
        private RenderTarget2D      _RenderTargetShaftsHalf;
        private RenderTarget2D      _RenderTargetShaftsFull;
        private RenderTarget2D      _RenderTargetFinal;
        private RenderTarget2D      _RenderTargetLuminance;
        private Texture2D           _BackgroundTexture;        

        private GraphicsDeviceManager graphics;
        private SpriteBatch         spriteBatch;
        private PostScreenFilters   _PostScreenFilters;
        
#if MAKE_VIDEO
        private int                 _FrameCounter = 0;
        private float               _OverallSpeed = 1f / 25f;
#else
        private float               _OverallSpeed = 1f;
#endif

        #region Gears        
        private Model               _GearModel1;
        private Model               _GearModel2;
        private Model               _GearModel3;
        private Vector3[ ]          _GearPosition;
        private float[ ]            _GearRotationAngle;
        private float[ ]            _GearRotationSpeed;
        private Effect              _GearMaterial;
        private TextureCube         _GearSpecularMap;
        private TextureCube         _GearDiffuseMap;
        #endregion

        // ---------------------------------------------------------
        public Game1( )
        {
            graphics = new GraphicsDeviceManager( this );
            graphics.PreferredBackBufferWidth = _Width;
            graphics.PreferredBackBufferHeight = _Height;
#if MAKE_VIDEO
            graphics.IsFullScreen = !false;
#else
            graphics.IsFullScreen = false;
#endif
            graphics.ApplyChanges( );
            Content.RootDirectory = "Content";
        }
        // ---------------------------------------------------------
        protected override void Initialize( )
        {
            SetupMatrices( );
            SetupRenderTargets( );

            _PostScreenFilters = new PostScreenFilters(
                _Width,
                _Height,
                GraphicsDevice,
                Content );

            InitializeGears( );
            base.Initialize( );
        }
        // ---------------------------------------------------------
        private void SetupMatrices( )
        {
            float AspectRatio = ( float ) _Width 
                / ( float ) _Height;

            Vector3 CameraPosition = new Vector3( 0f, 0f, 30f );

            _WorldProjection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.PiOver4,
                AspectRatio,
                1.0f,
                1000.0f );

            _WorldView = Matrix.CreateLookAt(
                CameraPosition,
                Vector3.Forward,
                Vector3.Up );
        }
        // ---------------------------------------------------------
        private void SetupRenderTargets( )
        {
            // just the gears with lighting and material
            _RenderTargetColor = new RenderTarget2D(
                 GraphicsDevice,
                 _Width,
                 _Height,
                 1,
                 SurfaceFormat.HalfVector4 );

            // post-processing: only luminance values above certain
            // threshold
            _RenderTargetLuminance = new RenderTarget2D(
                GraphicsDevice,
                _Width,
                _Height,
                1,
                SurfaceFormat.HalfVector4 );

            // temporal render target for additive blending
            _RenderTargetTemp = new RenderTarget2D(
                GraphicsDevice,
                _Width,
                _Height,
                1,
                SurfaceFormat.Vector4 );

            // the gears drawn black
            _RenderTargetMask = new RenderTarget2D(
                GraphicsDevice,
                _Width,
                _Height,
                1,
                SurfaceFormat.HalfVector4 );

            // post-processing: the screen in half resolution
            _RenderTargetShaftsHalf = new RenderTarget2D(
                GraphicsDevice,
                _Width / 2,
                _Height / 2,
                1,
                SurfaceFormat.HalfVector4 );

            // post-processing: the screen scaled to full 
            // resolution
            _RenderTargetShaftsFull = new RenderTarget2D(
                GraphicsDevice,
                _Width,
                _Height,
                1,
                SurfaceFormat.HalfVector4 );

            // all render targets combined
            _RenderTargetFinal = new RenderTarget2D(
                GraphicsDevice,
                _Width,
                _Height,
                1,
                SurfaceFormat.HalfVector4 );
        }
        // ---------------------------------------------------------
        private void InitializeGears( )
        {
            _GearPosition = new Vector3[ 3 ];
            _GearPosition[ 0 ] = new Vector3( );
            _GearPosition[ 0 ].X = -3f;
            _GearPosition[ 0 ].Y = 10f;
            _GearPosition[ 0 ].Z = -15f;

            _GearPosition[ 1 ] = new Vector3( );
            _GearPosition[ 1 ].X = 7f;
            _GearPosition[ 1 ].Y = 4f;
            _GearPosition[ 1 ].Z = -30f;

            _GearPosition[ 2 ] = new Vector3( );
            _GearPosition[ 2 ].X = 15f;
            _GearPosition[ 2 ].Y = 12f;
            _GearPosition[ 2 ].Z = -40f;

            _GearRotationAngle = new float[ 3 ];
            _GearRotationAngle[ 0 ] = 0.0f;
            _GearRotationAngle[ 1 ] = 0.0f;
            _GearRotationAngle[ 2 ] = 0.0f;
            _GearRotationSpeed = new float[ 3 ];
            _GearRotationSpeed[ 0 ] = 0.002f;
            _GearRotationSpeed[ 1 ] = 0.003f;
            _GearRotationSpeed[ 2 ] = 0.001f;
        }
        // ---------------------------------------------------------
        protected override void LoadContent( )
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch( GraphicsDevice );
            _GearMaterial = Content.Load<Effect>( "Effects\\GearMaterial" );
            _GearDiffuseMap = Content.Load<TextureCube>( "Textures\\Uffizi_DiffuseCubeMap" );
            _GearSpecularMap = Content.Load<TextureCube>( "Textures\\Uffizi_SpecularCubeMap" );
            _EffectWorld = Content.Load<Effect>( "Effects\\WorldCoordinates" );
            _EffectBlack = Content.Load<Effect>( "Effects\\BlackShader" );
            _EffectShadowMap = Content.Load<Effect>( "Effects\\ShadowMap" );
            _BackgroundTexture = Content.Load<Texture2D>( "Textures\\SunBackground" );
            _GearModel1 = Content.Load<Model>( "Models\\gear1" );
            _GearModel2 = Content.Load<Model>( "Models\\gear2" );
            _GearModel3 = Content.Load<Model>( "Models\\gear3" );
        }
        // ---------------------------------------------------------
        private void RenderScene( 
            RenderTarget2D Destination,
            Effect Effect,
            Matrix View,
            Matrix Projection )
        {
            // world, world inverse transpose and view inverse
            Matrix World, WorldIT, ViewI;

            // apply effect parameter to model
            Model CurModel = _GearModel1;
            foreach( ModelMesh mesh in CurModel.Meshes )
            {
                foreach ( ModelMeshPart part in mesh.MeshParts )
                {
                    part.Effect = Effect;
                }
            }

            // save render states
            bool OldDepthBufferEnable = GraphicsDevice.RenderState.DepthBufferEnable;
            CullMode OldCullMode = GraphicsDevice.RenderState.CullMode;
            GraphicsDevice.RenderState.DepthBufferEnable = true;
            GraphicsDevice.RenderState.CullMode = CullMode.None;
            GraphicsDevice.SetRenderTarget( 0, Destination );

            GraphicsDevice.Clear(
                ClearOptions.Target,
                new Vector4( 0f, 0f, 0f, 0f ),
                1,
                0 );

            // cube textures for the gear material
            GraphicsDevice.Textures[ 0 ] = _GearSpecularMap;
            GraphicsDevice.Textures[ 1 ] = _GearDiffuseMap;       
             
            // draw all three gears
            int NumGears = 3;
            for( int i = 0; i < NumGears; ++i )
            {
                World = Matrix.CreateScale( 0.05f, 0.05f, 0.05f )                    
                    * Matrix.CreateRotationZ( _GearRotationAngle[ i ] )
                    * Matrix.CreateRotationY( 0.4f )
                    * Matrix.CreateTranslation( _GearPosition[ i ] );

                WorldIT = Matrix.Invert( Matrix.Transpose( World ) );
                ViewI = Matrix.Invert( View );                

                foreach ( ModelMesh mesh in CurModel.Meshes )
                {
                    foreach ( Effect currentEffect in mesh.Effects )
                    {
                        currentEffect.CurrentTechnique = currentEffect.Techniques[ 0 ];
                        currentEffect.Parameters[ "gWorldXf" ].SetValue( World );
                        currentEffect.Parameters[ "gViewXf" ].SetValue( View );
                        currentEffect.Parameters[ "gProjectionXf" ].SetValue( Projection );
                        currentEffect.Parameters[ "gWorldITXf" ].SetValue( WorldIT );
                        currentEffect.Parameters[ "gViewIXf" ].SetValue( ViewI );
                        currentEffect.Parameters[ "gExposure" ].SetValue( _GearExposure );
                    } // foreach
                    mesh.Draw( );
                } // foreach
            } // for     

            // restore render states
            GraphicsDevice.Textures[ 0 ] = null;
            GraphicsDevice.Textures[ 1 ] = null;
            GraphicsDevice.SetRenderTarget( 0, null );
            GraphicsDevice.RenderState.DepthBufferEnable = OldDepthBufferEnable;
            GraphicsDevice.RenderState.CullMode = OldCullMode;
        }
        // ---------------------------------------------------------
        protected override void UnloadContent( )
        {
            // TODO: Unload any non ContentManager content here
        }
        // ---------------------------------------------------------
        protected override void Update( GameTime gameTime )
        {
            // Allows the game to exit
            if ( GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed )
                this.Exit( );

            // TODO: Add your update logic here          
#if ROTATE
            int NumGears = 3;
            for( int i = 0; i < NumGears; ++i )
            {
                if( _GearRotationAngle[ i ] < Math.PI * 2 )
                {
                    _GearRotationAngle[ i ] += 
                        _GearRotationSpeed[ i ] * _OverallSpeed;
                }
                else
                {
                    _GearRotationAngle[ i ] = 0f;
                }
            }
#endif

            base.Update( gameTime );
        }
        // ---------------------------------------------------------
        private void DrawLightShafts( )
        {
        }
        // ---------------------------------------------------------
        protected override void Draw( GameTime gameTime )
        {
            // render the scene in black
            RenderScene( 
                _RenderTargetMask, 
                _EffectBlack,
                _WorldView,
                _WorldProjection );
            // render the scene with material and lighting
            RenderScene( 
                _RenderTargetColor, 
                _GearMaterial,
                _WorldView,
                _WorldProjection );            

            // additively render the mask over the background
            GraphicsDevice.SetRenderTarget( 0, _RenderTargetTemp );
            GraphicsDevice.Clear( Color.Black );
            spriteBatch.Begin( 
                SpriteBlendMode.AlphaBlend,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState );
            spriteBatch.Draw( _BackgroundTexture, new Rectangle( 0, 0, _Width, _Height ), Color.White );
            spriteBatch.Draw( _RenderTargetMask.GetTexture( ), Vector2.Zero, Color.Black );
            spriteBatch.End( );
            GraphicsDevice.SetRenderTarget( 0, null );

            // create mipmap levels of the resulting target
            RenderTarget2D[ ] MipLevels = 
                _PostScreenFilters.CreateMipMapLevels( 
                _RenderTargetTemp );

            //for( int i = 0; i < MipLevels.Length; ++i )
            //{
            //    MipLevels[ i ].GetTexture( ).Save( "c:/level"+i+".png", ImageFileFormat.Png );
            //}

            // perform the light shafts on half of the resolution
            _PostScreenFilters.LightShafts(
                MipLevels[ 0 ],
                _RenderTargetShaftsHalf,
                _LightMapPosition,
                1.0f,
                _LightShaftDecay,
                1.0f,
                _LightShaftExposure );

            // up-scale the effect
            _PostScreenFilters.HalfToFullscreen(
                 _RenderTargetShaftsHalf,
                 _RenderTargetShaftsFull );

            //_RenderTargetShaftsFull.GetTexture( ).Save( "c:/full.png", ImageFileFormat.Png );

            // extract luminance values above threshold
            _PostScreenFilters.ExtractHighLuminance(
                _RenderTargetShaftsFull,
                _RenderTargetLuminance,
                _LuminanceThreshold,
                _LuminanceScaleFactor );
            
            // create mipmap levels of extracted values and 
            // additively blend them
            RenderTarget2D RT = _PostScreenFilters.AdditiveBlend(
                _RenderTargetLuminance );

            //_RenderTargetColor.GetTexture( ).Save( "c:/color.png", ImageFileFormat.Png );

            // combine all the targets
            GraphicsDevice.SetRenderTarget( 0, _RenderTargetFinal );
            GraphicsDevice.Clear( ClearOptions.Target, Vector4.Zero, 1, 0 );
            spriteBatch.Begin(
                SpriteBlendMode.Additive,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState );
            spriteBatch.Draw( _RenderTargetShaftsFull.GetTexture( ), Vector2.Zero, Color.White );
            spriteBatch.Draw( _RenderTargetColor.GetTexture( ), Vector2.Zero, Color.White );
            spriteBatch.Draw( RT.GetTexture( ), Vector2.Zero, Color.White );
            spriteBatch.End( );
            GraphicsDevice.SetRenderTarget( 0, null );

            //_RenderTargetTemp.GetTexture( ).Save( "mask.png", ImageFileFormat.Png );

            // draw them on the screen
            spriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState );
            spriteBatch.Draw(
                _RenderTargetFinal.GetTexture( ),                
                Vector2.Zero,
                Color.White );
            spriteBatch.End( );

#if MAKE_VIDEO
            _RenderTargetFinal.GetTexture( ).Save( "d:/seq"+_FrameCounter+".jpg", ImageFileFormat.Jpg );
            _FrameCounter++;
#endif

#if MAKE_SCREENSHOT
            _RenderTargetFinal.GetTexture( ).Save( "c:/LightShaftEffect.png", ImageFileFormat.Png );
#endif

            base.Draw( gameTime );
        }
        // ---------------------------------------------------------
        // ---------------------------------------------------------
        // ---------------------------------------------------------
    }
}
