//BSD License  
//DXGfx® - http://www.eteractions.com
//Copyright (c) 2005
//by Guillaume Randon
//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.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;
using Microsoft.DirectX.DirectSound;
using DXGfxLib;
using Game;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace Editor
{
    public partial class TDView : UserControl, IFrameworkCallback, IDeviceCreation
    {
        // Variables
        private Framework sampleFramework = null; // Framework for samples
        private Microsoft.DirectX.Direct3D.Font statsFont = null; // Font for drawing text
        private Sprite textSprite = null; // Sprite for batching text calls
        //private ModelViewerCamera camera = new ModelViewerCamera(); // A model viewing camera
        private FreeCamera camera = new FreeCamera();
        private bool isHelpShowing = true; // If true, renders the UI help text
        private Dialog hud = null; // dialog for standard controls
        private Dialog missionUi = null;
        private Matrix worldCenter;

        // HUD Ui Control constants
        private const int ToggleFullscreen = 1;
        private const int ToggleReference = 3;
        private const int ChangeDevice = 4;

        // DXGfxLib related variables
        public OutDoorScene outdoorScene;
        //private Matrix computedViewMat;
        private const int MissionText = 1;

        // Wordl related variables
        private Tiger tiger;
        private Vegetals veg;
        private double lastAIUpdate = 0.0f;
        private Hashtable worldEntities = new Hashtable();

        // Need a soundDevice in order to be able to play sounds.
        private Microsoft.DirectX.DirectSound.Device soundDevice = null;

        // We want to be aware of the Mediator, as UI can impact what is drawn and click on scene can impact UI.
        private Mediator mediator = null;

        public bool play = false;

        private StateBlock defaultState = null;

        VertexBuffer pickRayVBuff = null;

        /// <summary>
        /// We need a property to deactivate rendering while we load a scene and prepare it.
        /// </summary>
        private bool readyToGo = false;

        public bool ReadyToGo
        {
            get { return readyToGo; }
            set { readyToGo = value; }
        }

        public TDView()
        {
            InitializeComponent();

            sampleFramework = new Framework();

            // Create dialogs
            hud = new Dialog(sampleFramework);
            missionUi = new Dialog(sampleFramework);

            DXGfxManager.GetGlobalInstance().Init(sampleFramework);

            

            sampleFramework.Disposing += new EventHandler(OnDestroyDevice);
            sampleFramework.DeviceLost += new EventHandler(OnLostDevice);
            sampleFramework.DeviceCreated += new DeviceEventHandler(OnCreateDevice);
            sampleFramework.DeviceReset += new DeviceEventHandler(OnResetDevice);

            sampleFramework.SetWndProcCallback(new WndProcCallback(OnMsgProc));

            sampleFramework.SetCallbackInterface(this);

            InitializeApplication();

            sampleFramework.SetWindow(this, this, this, true);

            InitSound();

            sampleFramework.CreateDevice(0, true, Framework.DefaultSizeWidth, Framework.DefaultSizeHeight, this);
        }

        public void LetItRun()
        {
            sampleFramework.MainLoop();
        }

        /// <summary>
        /// Initializes the application
        /// </summary>
        public void InitializeApplication()
        {
            int y = 10;

            // Initialize the HUD
            Microsoft.Samples.DirectX.UtilityToolkit.Button fullScreen = hud.AddButton(ToggleFullscreen, "Toggle full screen", 35, y, 125, 22);
            Microsoft.Samples.DirectX.UtilityToolkit.Button toggleRef = hud.AddButton(ToggleReference, "Toggle reference (F3)", 35, y += 24, 125, 22);
            Microsoft.Samples.DirectX.UtilityToolkit.Button changeDevice = hud.AddButton(ChangeDevice, "Change Device (F2)", 35, y += 24, 125, 22);
            // Hook the button events for when these items are clicked
            fullScreen.Click += new EventHandler(OnFullscreenClicked);
            toggleRef.Click += new EventHandler(OnRefClicked);
            changeDevice.Click += new EventHandler(OnChangeDevicClicked);

            // Our own UI
            Microsoft.Samples.DirectX.UtilityToolkit.Button missionText = missionUi.AddButton(MissionText, "Salut!!", 0, 0, 170, 120);
            missionText.Click += new EventHandler(missionText_Click);
        }

        /// <summary>
        /// This method can be used to set the mediator.
        /// </summary>
        /// <param name="mediator"></param>
        public void SetMediator(Mediator mediator)
        {
            this.mediator = mediator;
        }

        /// <summary>
        /// Called during device initialization, this code checks the device for some 
        /// minimum set of capabilities, and rejects those that don't pass by returning false.
        /// In this implementation we require at least support for shader 1.1 and we require
        /// a backbuffer format which does support alpha blending.
        /// </summary>
        public bool IsDeviceAcceptable(Microsoft.DirectX.Direct3D.Caps caps, Format adapterFormat, Format backBufferFormat, bool windowed)
        {
            //// No fallback, need at least PS1.1
            //if (caps.PixelShaderVersion < new Version(1, 1))
            //    return false;

            //// Skip back buffer formats that don't support alpha blending
            //if (!Manager.CheckDeviceFormat(caps.AdapterOrdinal, caps.DeviceType, adapterFormat,
            //    Usage.QueryPostPixelShaderBlending, ResourceType.Textures, backBufferFormat))
            //    return false;

            return true;
        }

        /// <summary>
        /// This callback function is called immediately before a device is created to allow the 
        /// application to modify the device settings. The supplied settings parameter 
        /// contains the settings that the framework has selected for the new device, and the 
        /// application can make any desired changes directly to this structure.  Note however that 
        /// the sample framework will not correct invalid device settings so care must be taken 
        /// to return valid device settings, otherwise creating the Device will fail.  
        /// </summary>
        public void ModifyDeviceSettings(DeviceSettings settings, Microsoft.DirectX.Direct3D.Caps caps)
        {
//            // If device doesn't support HW T&L or doesn't support 1.1 vertex shaders in HW 
//            // then switch to SWVP.
//            if ((!caps.DeviceCaps.SupportsHardwareTransformAndLight) ||
//                (caps.VertexShaderVersion < new Version(1, 1)))
//            {
//                settings.BehaviorFlags = CreateFlags.SoftwareVertexProcessing;
//            }
//            else
//            {
//                settings.BehaviorFlags = CreateFlags.HardwareVertexProcessing;
//            }

//            // This application is designed to work on a pure device by not using 
//            // any get methods, so create a pure device if supported and using HWVP.
//            if ((caps.DeviceCaps.SupportsPureDevice) &&
//                ((settings.BehaviorFlags & CreateFlags.HardwareVertexProcessing) != 0))
//                settings.BehaviorFlags |= CreateFlags.PureDevice;

//            // Debugging vertex shaders requires either REF or software vertex processing 
//            // and debugging pixel shaders requires REF.  
//#if(DEBUG_VS)
//            if (settings.DeviceType != DeviceType.Reference )
//            {
//                settings.BehaviorFlags &= ~CreateFlags.HardwareVertexProcessing;
//                settings.BehaviorFlags |= CreateFlags.SoftwareVertexProcessing;
//            }
//#endif
//#if(DEBUG_PS)
              //settings.DeviceType = DeviceType.Reference;
//#endif

//            // For the first device created if its a REF device, optionally display a warning dialog box
//            if (settings.DeviceType == DeviceType.Reference)
//            {
//                Utility.DisplaySwitchingToRefWarning(sampleFramework, "Sample");
//            }
        }

        /// <summary>
        /// This event will be fired immediately after the Direct3D device has been 
        /// created, which will happen during application initialization and windowed/full screen 
        /// toggles. This is the best location to create Pool.Managed resources since these 
        /// resources need to be reloaded whenever the device is destroyed. Resources created  
        /// here should be released in the Disposing event. 
        /// </summary>
        private void OnCreateDevice(object sender, DeviceEventArgs e)
        {
            // Initialize the stats font
            statsFont = ResourceCache.GetGlobalInstance().CreateFont(e.Device, 15, 0, FontWeight.Bold, 1, false, CharacterSet.Default,
                Precision.Default, FontQuality.Default, PitchAndFamily.FamilyDoNotCare | PitchAndFamily.DefaultPitch
                , "Arial");

            // Define DEBUG_VS and/or DEBUG_PS to debug vertex and/or pixel shaders with the 
            // shader debugger. Debugging vertex shaders requires either REF or software vertex 
            // processing, and debugging pixel shaders requires REF.  The 
            // ShaderFlags.Force*SoftwareNoOptimizations flag improves the debug experience in the 
            // shader debugger.  It enables source level debugging, prevents instruction 
            // reordering, prevents dead code elimination, and forces the compiler to compile 
            // against the next higher available software target, which ensures that the 
            // unoptimized shaders do not exceed the shader model limitations.  Setting these 
            // flags will cause slower rendering since the shaders will be unoptimized and 
            // forced into software.  See the DirectX documentation for more information about 
            // using the shader debugger.
            ShaderFlags shaderFlags = ShaderFlags.NotCloneable;
#if(DEBUG_VS)
            shaderFlags |= ShaderFlags.ForceVertexShaderSoftwareNoOptimizations;
#endif
#if(DEBUG_PS)
            shaderFlags |= ShaderFlags.ForcePixelShaderSoftwareNoOptimizations;
#endif

            // Before everything let's notify the DXGfxManager
            DXGfxManager.GetGlobalInstance().OnCreateDevice(sender, e);

            // Setup the camera's view parameters
            camera.SetViewParameters(new Vector3(0.0f, 0.0f, -5.0f), Vector3.Empty);

            pickRayVBuff = new VertexBuffer(e.Device, CustomVertex.PositionOnly.StrideSize * 2, Usage.Dynamic, CustomVertex.PositionOnly.Format, Pool.SystemMemory);
        }

        /// <summary>
        /// This callback function will be called immediately after the Direct3D device has 
        /// been destroyed, which generally happens as a result of application termination or 
        /// windowed/full screen toggles. Resources created in the OnCreateDevice callback 
        /// should be released here, which generally includes all Pool.Managed resources. 
        /// </summary>
        private void OnDestroyDevice(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// This event function will be called fired after the Direct3D device has 
        /// entered a lost state and before Device.Reset() is called. Resources created
        /// in the OnResetDevice callback should be released here, which generally includes all 
        /// Pool.Default resources. See the "Lost Devices" section of the documentation for 
        /// information about lost devices.
        /// </summary>
        private void OnLostDevice(object sender, EventArgs e)
        {
            if (outdoorScene != null)
            {
                outdoorScene.CleanOnLostDevice();
            }

            if (textSprite != null)
            {
                textSprite.Dispose();
                textSprite = null;
            }
        }

        /// <summary>
        /// This event will be fired immediately after the Direct3D device has been 
        /// reset, which will happen after a lost device scenario. This is the best location to 
        /// create Pool.Default resources since these resources need to be reloaded whenever 
        /// the device is lost. Resources created here should be released in the OnLostDevice 
        /// event. 
        /// </summary>
        private void OnResetDevice(object sender, DeviceEventArgs e)
        {
            SurfaceDescription desc = e.BackBufferDescription;
            // Create a sprite to help batch calls when drawing many lines of text
            textSprite = new Sprite(e.Device);

            // Setup the camera's projection parameters
            float aspectRatio = (float)desc.Width / (float)desc.Height;
            camera.SetProjectionParameters((float)Math.PI / 4, aspectRatio, 0.1f, 1000.0f);
            camera.SetViewParameters(new Vector3(0.0f, 5.0f, -30.0f), new Vector3(0.0f, 0.0f, 0.0f));
            camera.IsPositionMovementEnabled = true;

            DXGfxManager.GetGlobalInstance().OnDeviceReset(sender, e);

            // Setup UI locations
            hud.SetLocation(desc.Width - 170, 0);
            hud.SetSize(170, 170);

            // Scene call
            if (outdoorScene != null)
            {
                outdoorScene.DeviceChanged(e.Device);
            }
            missionUi.SetLocation(0, desc.Height - 120);
            missionUi.SetSize(170, 120);
        }

        /// <summary>
        /// This callback function will be called once at the beginning of every frame. This is the
        /// best location for your application to handle updates to the scene, but is not 
        /// intended to contain actual rendering calls, which should instead be placed in the 
        /// OnFrameRender callback.  
        /// </summary>
        public void OnFrameMove(Microsoft.DirectX.Direct3D.Device device, double appTime, float elapsedTime)
        {
            // Update the camera's position based on user input 
            camera.FrameMove(elapsedTime);

            // If we want object to move we have to use the camera worldmatrix!
            //if (mediator.selectedNode != null)
            //{
            //    mediator.selectedNode.WorldMat = camera.WorldMatrix;
            //}
            // Let's update our tiger
            double tmp = appTime - lastAIUpdate;

            if (play)
            {
                DXGfxManager.GetGlobalInstance().CurrentInputHandler.FrameMove(elapsedTime);
                DXGfxManager.GetGlobalInstance().CallEvaluation(appTime, elapsedTime);
            }
            //if (tmp > 10)
            //{
            //tiger.UpdateAI(appTime, elapsedTime);
            //tiger.UpdateIA(elapsedTime, appTime);
            //lastAIUpdate = appTime;
            //}

            //if (tiger.mission.missionCompleted)
            //{
            //    missionUi.GetButton(MissionText).SetText("Mission Completed!!");
            //}
            //switch (tiger.currentState)
            //{
            //    case State.Bois:
            //        missionUi.GetButton(MissionText).SetText("Bois!!");
            //        break;
            //    case State.Dort:
            //        missionUi.GetButton(MissionText).SetText("Dort!!");
            //        break;
            //    case State.Explore:
            //        missionUi.GetButton(MissionText).SetText("Explore!!");
            //        break;
            //    case State.Fuit:
            //        missionUi.GetButton(MissionText).SetText("Fuit!!");
            //        break;
            //    case State.Mange:
            //        missionUi.GetButton(MissionText).SetText("Mange!!");
            //        break;
            //    default:
            //        missionUi.GetButton(MissionText).SetText("Default!!");
            //        break;
            //}
        }

        /// <summary>
        /// When in edit mode all the rendering is performed here. When in edit mode
        /// we don't need to update our node quickly, we don't need to move at interactive
        /// speed whe just need to render on WM_PAINT message.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(PaintEventArgs e)
        {
            if(sampleFramework==null)
                return;
            if(sampleFramework.Device==null)
                return;
            Render(sampleFramework.Device, 0, 0);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // Just don't do anything !! base.OnPaintBackground(e);
        }

        /// <summary>
        /// This callback function will be called at the end of every frame to perform all the 
        /// rendering calls for the scene, and it will also be called if the window needs to be 
        /// repainted. After this function has returned, the sample framework will call 
        /// Device.Present to display the contents of the next buffer in the swap chain
        /// </summary>
        public void OnFrameRender(Microsoft.DirectX.Direct3D.Device device, double appTime, float elapsedTime)
        {
            Render(device, appTime, elapsedTime);
        }

        private void Render(Microsoft.DirectX.Direct3D.Device device, double appTime, float elapsedTime)
        {
            // Clear the render target and the zbuffer and return
            device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, 0x00424B79, 1.0f, 0);

            bool beginSceneCalled = false;

            if (defaultState == null)
            {
                defaultState = new StateBlock(device, StateBlockType.All);
                defaultState.Capture();
            }

            defaultState.Apply();



            if (outdoorScene == null)
            {
                return;
            }

            // Get the world matrix
            //Matrix worldMatrix = worldCenter * camera.WorldMatrix;
            //computedViewMat = Matrix.Multiply(camera.WorldMatrix, camera.ViewMatrix);
            Camera tmpCamera = null;
            if (play)
            {
                tmpCamera = DXGfxManager.GetGlobalInstance().CurrentCamera;
            }
            else
            {
                tmpCamera = camera;
            }


            outdoorScene.Update(device, tmpCamera.ViewMatrix, tmpCamera.ProjectionMatrix, appTime, elapsedTime);


            outdoorScene.RenderScene(device, outdoorScene.currentFrustrum);
        }

        public void InitSound()
        {
            soundDevice = new Microsoft.DirectX.DirectSound.Device();
            soundDevice.SetCooperativeLevel(this.sampleFramework.Window.Handle, CooperativeLevel.Priority);
        }

        /// <summary>
        /// Render the help and statistics text. This function uses the Font object for 
        /// efficient text rendering.
        /// </summary>
        private void RenderText()
        {
            TextHelper txtHelper = new TextHelper(statsFont, textSprite, 15);

            // Output statistics
            txtHelper.Begin();
            txtHelper.SetInsertionPoint(5, 5);
            txtHelper.SetForegroundColor(System.Drawing.Color.Yellow);
            txtHelper.DrawTextLine(sampleFramework.FrameStats);
            txtHelper.DrawTextLine(sampleFramework.DeviceStats);

            txtHelper.SetForegroundColor(System.Drawing.Color.White);

            txtHelper.End();
        }

        /// <summary>
        /// As a convenience, the sample framework inspects the incoming windows messages for
        /// keystroke messages and decodes the message parameters to pass relevant keyboard
        /// messages to the application.  The framework does not remove the underlying keystroke 
        /// messages, which are still passed to the application's MsgProc callback.
        /// </summary>
        public void OnKeyEvent(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case System.Windows.Forms.Keys.F1:
                    isHelpShowing = !isHelpShowing;
                    break;
            }
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);

            WorldLocation destinationLocation = new WorldLocation();

            Vector2 point = new Vector2(e.X, e.Y);

            Vector3 rayO; Vector3 rayD;

            // Some work to do on the Viewport!!
            Viewport viewport = sampleFramework.Device.Viewport;
            viewport.Width = this.Width;
            viewport.Height = this.Height;

            if (play)
            {
                DXGfxManager.GetGlobalInstance().height = this.Height;
                DXGfxManager.GetGlobalInstance().width = this.Width;
                DXGfxManager.GetGlobalInstance().CurrentInputHandler.OnMouseDoubleClick(e);
            }
            else
            {
                MathUtil.RayFromScreenPos(viewport, camera.ProjectionMatrix, camera.ViewMatrix, point, out rayO, out rayD);
                List<SceneObject> theList = new List<SceneObject>();

                bool foundnothing = true;

                if (outdoorScene.Intersect(rayO, rayD, ref theList))
                {
                    foreach (SceneObject obj in theList)
                    {
                        // TODO : Do we want the objet that we have selected to be selected in the editor?
                    }
                }

                if (foundnothing)
                {
                    // TODO : If no object is selected maybe we want the selected object to be put on ground where we have clicked.
                    Vector3 clickedPosition = outdoorScene.PickEnvironmentPosition(viewport, camera.ProjectionMatrix, camera.ViewMatrix, point);

                    if (mediator != null)
                    {
                        SceneNode selectedNode = mediator.selectedNode;

                        if (selectedNode != null)
                        {
                            selectedNode.Position = clickedPosition;
                        }
                    }
                }

                // Let's put values in the pickRayVBuff
                CustomVertex.PositionOnly[] points = new CustomVertex.PositionOnly[2];
                points[0].Position = new Vector3(-10, -10, -10);
                points[1].Position = new Vector3(10, 10, 10);

                GraphicsStream stm = pickRayVBuff.Lock(0, 0, 0);
                stm.Write(points);
                pickRayVBuff.Unlock();
            }
        }

        /// <summary>
        /// We override this method in order to pass window messages to our camera (update arcball and everything).
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            bool noFurtherProcessing = false;

            OnMsgProc(m.HWnd,
                (Microsoft.Samples.DirectX.UtilityToolkit.NativeMethods.WindowMessage)m.Msg,
                m.WParam, m.LParam, ref noFurtherProcessing);
        }

        /// <summary>
        /// Before handling window messages, the sample framework passes incoming windows 
        /// messages to the application through this callback function. If the application sets 
        /// noFurtherProcessing to true, the sample framework will not process the message
        /// </summary>
        public IntPtr OnMsgProc(IntPtr hWnd, NativeMethods.WindowMessage msg, IntPtr wParam, IntPtr lParam, ref bool noFurtherProcessing)
        {
            // Give the dialog a chance to handle the message first
            noFurtherProcessing = hud.MessageProc(hWnd, msg, wParam, lParam);
            if (noFurtherProcessing)
                return IntPtr.Zero;

            noFurtherProcessing = missionUi.MessageProc(hWnd, msg, wParam, lParam);
            if (noFurtherProcessing)
                return IntPtr.Zero;

            // Pass all remaining windows messages to camera so it can respond to user input
            if (play)
            {
                DXGfxManager.GetGlobalInstance().CurrentInputHandler.HandleMessages(hWnd, msg, wParam, lParam);
            }
            else
            {
                camera.HandleMessages(hWnd, msg, wParam, lParam);
            }

            return IntPtr.Zero;
        }

        #region App Idle Events
        /// <summary>Fired when the application first gets idle</summary>
        public void OnApplicationIdle(object sender, EventArgs e)
        {
            while (AppStillIdle)
            {
                // Render a frame during idle time (no messages are waiting)
                sampleFramework.Render3DEnvironment();
            }
        }
        /// <summary>Checks to see if the application is still idle</summary>
        private bool AppStillIdle
        {
            get
            {
                NativeMethods.Message msg;
                return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
            }
        }
        #endregion

        void missionText_Click(object sender, EventArgs e)
        {
            missionUi.GetButton(MissionText).SetText("Button Clicked!!");
        }

        /// <summary>Called when the change device button is clicked</summary>
        private void OnChangeDevicClicked(object sender, EventArgs e)
        {
            sampleFramework.ShowSettingsDialog(!sampleFramework.IsD3DSettingsDialogShowing);
        }

        /// <summary>Called when the full screen button is clicked</summary>
        private void OnFullscreenClicked(object sender, EventArgs e)
        {
            sampleFramework.ToggleFullscreen();
        }

        /// <summary>Called when the ref button is clicked</summary>
        private void OnRefClicked(object sender, EventArgs e)
        {
            sampleFramework.ToggleReference();
        }
    }
}
