// File: NeoBreakOut.cs
// Author: Sonhui Schweitzer
// Date: January 25th, 2005
// Project: CS470 Applied Software Developement Project

using System;
using BreakOut;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using DirectInput=Microsoft.DirectX.DirectInput;
using DirectSound=Microsoft.DirectX.DirectSound;
using Microsoft.Samples.DirectX.UtilityToolkit;

namespace NeoBreakOutSample
{
	// NeoBreakout class owns the game model and manages the interaction between the system and the game.
	// keeps a framework for interacting with the window and 
	// the 3d device, and owns and manages both the sound and input devices.
    public class NeoBreakOut : IFrameworkCallback, IDeviceCreation
    {
		// Dialogs
		private Dialog m_oMainMenu;

		private const int MainMenu_TitleStatic = 1;
		private const int MainMenu_PlayGameButton = 2;
		private const int MainMenu_FullscreenToggleButton = 3;
		private const int MainMenu_QuitButton = 4;

		private System.Drawing.Rectangle m_oMainMenu_TitleStatic = new System.Drawing.Rectangle( 0 , 0 , 800 , 200 );
		private System.Drawing.Rectangle m_oMainMenu_PlayGameButton = new System.Drawing.Rectangle( 250 , 200 , 300 , 50 );
		private System.Drawing.Rectangle m_oMainMenu_FullscreenToggleButton = new System.Drawing.Rectangle( 250 , 250 , 300 , 50 );
		private System.Drawing.Rectangle m_oMainMenu_QuitButton = new System.Drawing.Rectangle( 350 , 500 , 100 , 50 );

		// constant time step size
		private const float m_fStepSize = 1.0f / 240.0f;
		// time remaining for the last update (less than step size)
		private float m_fRemainder;

		// vars for pausing
		private bool m_bPause = false;

		private Framework m_oFramework = null; // Framework for samples
		private Font m_oGameFont = null; // Font for drawing text
		private Sprite m_oGameTextSprite = null; // Sprite for batching text calls

		// input and sound devices
		private DirectInput.Device m_oMouse = null;
		private DirectSound.Device m_oSound = null;

		// the breakout game model
		private Game m_oGame = null;

		/// <summary>
		/// Constructor creates a new instance of the NeoBreakOut class.
		/// </summary>
		/// <param name="oFramework">The framework is used manage the window and 3D device</param>
		public NeoBreakOut( Framework oFramework ) 
        {
			// start out with no remaining time
			m_fRemainder = 0.0f;

			// Store framework
            m_oFramework = oFramework;

			// construct the game model (once per execution)
			m_oGame = new Game();

			InitDialogs();
		}

		/// <summary>
		/// Destructor cleans up member resources
		/// </summary>
		~NeoBreakOut()
		{
			if ( m_oMouse != null )
			{
				m_oMouse.Dispose();
			}
			if ( m_oSound != null )
			{
				m_oSound.Dispose();
			}
		}

        /// <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.
        /// </summary>
        public bool IsDeviceAcceptable( Caps oCaps , Format oAdapterFormat , Format oBackBufferFormat , bool bWindowed )
        {
            // Skip back buffer formats that don't support alpha blending
			if ( !Manager.CheckDeviceFormat( oCaps.AdapterOrdinal , oCaps.DeviceType , oAdapterFormat , Usage.QueryPostPixelShaderBlending , ResourceType.Textures , oBackBufferFormat ) )
			{
				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 oSettings , Caps oCaps )
        {
			// constrain render rate to the monitor/adapter refresh rate for smoother animation and to eliminate tearing
			oSettings.presentParams.PresentationInterval = PresentInterval.One;
            // If device doesn't support HW T&L or doesn't support 1.1 vertex shaders in HW 
            // then switch to SWVP.
            if ( ( !oCaps.DeviceCaps.SupportsHardwareTransformAndLight ) || ( oCaps.VertexShaderVersion < new Version( 1 , 1 ) ) )
            {
                oSettings.BehaviorFlags = CreateFlags.SoftwareVertexProcessing;
            }
            else
            {
                oSettings.BehaviorFlags = CreateFlags.HardwareVertexProcessing;
            }
        }

        /// <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 oSender , DeviceEventArgs oDeviceEventArgs )
        {
            // Initialize the stats font
            m_oGameFont = ResourceCache.GetGlobalInstance().CreateFont( oDeviceEventArgs.Device , 30 , 0 , FontWeight.Bold , 1 , false , CharacterSet.Default , Precision.Default , FontQuality.Default , PitchAndFamily.FamilyDoNotCare | PitchAndFamily.DefaultPitch , "Verdana" );
        }
        
        /// <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 oSender , DeviceEventArgs oDeviceEventArgs )
        {
            SurfaceDescription oDesc = oDeviceEventArgs.BackBufferDescription;
            // Create a sprite to help batch calls when drawing many lines of text
			if ( m_oGameTextSprite != null )
			{
				m_oGameTextSprite.Dispose();
				m_oGameTextSprite = null;
			}
			m_oGameTextSprite = new Sprite( oDeviceEventArgs.Device );

			oDeviceEventArgs.Device.Transform.View = Matrix.LookAtLH( new Vector3( 0.0f , 0.0f , -1.0f ) , Vector3.Empty , new Vector3( 0.0f , 1.0f , 0.0f ) );
			oDeviceEventArgs.Device.Transform.Projection = Matrix.OrthoLH( 4.0f , 3.0f , 0.1f , 10.0f );

			LayoutDialogs();
            // Setup UI locations
			m_oGame.OnResetDirect3DDevice( oDeviceEventArgs.Device );

		}

		private void InitDialogs()
		{
			Button oButton;
			StaticText oStatic;

			m_oMainMenu = new Dialog( m_oFramework );
			m_oMainMenu.IsMinimized = false;

			m_oMainMenu.SetFont( 0 , "Verdana" , 15 , FontWeight.Normal );

			m_oMainMenu.SetFont( 1 , "Verdana" , 30 , FontWeight.Bold );

			oStatic = m_oMainMenu.AddStatic( MainMenu_TitleStatic , "Main Menu" , m_oMainMenu_TitleStatic.Left , m_oMainMenu_TitleStatic.Top , m_oMainMenu_TitleStatic.Width , m_oMainMenu_TitleStatic.Height );
			oStatic[ 0 ].FontIndex = 1;
			oButton = m_oMainMenu.AddButton( MainMenu_PlayGameButton , "Play Game" , m_oMainMenu_PlayGameButton.Left , m_oMainMenu_PlayGameButton.Top , m_oMainMenu_PlayGameButton.Width , m_oMainMenu_PlayGameButton.Height );
			oButton[ 0 ].FontIndex = 1;
			oButton[ 1 ].FontIndex = 1;
			oButton.Click += new EventHandler( MainMenu_PlayGameButtonClicked );
			oButton = m_oMainMenu.AddButton( MainMenu_FullscreenToggleButton , "Fullscreen Toggle" , m_oMainMenu_FullscreenToggleButton.Left , m_oMainMenu_FullscreenToggleButton.Top , m_oMainMenu_FullscreenToggleButton.Width , m_oMainMenu_FullscreenToggleButton.Height );
			oButton[ 0 ].FontIndex = 1;
			oButton[ 1 ].FontIndex = 1;
			oButton.Click += new EventHandler(MainMenu_FullscreenToggleButtonClicked);
			oButton = m_oMainMenu.AddButton( MainMenu_QuitButton , "Quit" , m_oMainMenu_QuitButton.Left , m_oMainMenu_QuitButton.Top , m_oMainMenu_QuitButton.Width , m_oMainMenu_QuitButton.Height );
			oButton[ 0 ].FontIndex = 1;
			oButton[ 1 ].FontIndex = 1;
			oButton.Click += new EventHandler( MainMenu_QuitButtonClicked );
		}

		private void LayoutDialogs()
		{
			m_oMainMenu.SetLocation( 0 , 0 );
			m_oMainMenu.SetSize( 800 , 600 );
			m_oMainMenu.GetControl( MainMenu_TitleStatic ).SetLocation( m_oMainMenu_TitleStatic.Left , m_oMainMenu_TitleStatic.Top );
			m_oMainMenu.GetControl( MainMenu_TitleStatic ).SetSize( m_oMainMenu_TitleStatic.Width , m_oMainMenu_TitleStatic.Height );
			m_oMainMenu.GetControl( MainMenu_PlayGameButton ).SetLocation( m_oMainMenu_PlayGameButton.Left , m_oMainMenu_PlayGameButton.Top );
			m_oMainMenu.GetControl( MainMenu_PlayGameButton ).SetSize( m_oMainMenu_PlayGameButton.Width , m_oMainMenu_PlayGameButton.Height );
			m_oMainMenu.GetControl( MainMenu_QuitButton ).SetLocation( m_oMainMenu_QuitButton.Left , m_oMainMenu_QuitButton.Top );
			m_oMainMenu.GetControl( MainMenu_QuitButton ).SetSize( m_oMainMenu_QuitButton.Width , m_oMainMenu_QuitButton.Height );
		}

        /// <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 oSender , EventArgs oEventArgs )
        {
			if ( m_oGameTextSprite != null )
			{
				m_oGameTextSprite.Dispose();
				m_oGameTextSprite = null;
			}
			m_oGame.OnLostDirect3DDevice();
        }

        /// <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 oSender , EventArgs oEventArgs )
        {
        }

        /// <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( Device oDevice , double fAppTime , float fElapsedTime )
        {
			if ( !m_bPause && m_oMainMenu.IsMinimized )
			{
				try
				{
					// poll input and apply it
					m_oGame.UpdateInput( m_oMouse.CurrentMouseState );
				}
					// handle input device errors
				catch ( DirectInput.NotAcquiredException ) { try { m_oMouse.Acquire(); } 
																catch ( DirectInput.InputException ) { } }
				catch ( DirectInput.InputLostException ) { try { m_oMouse.Acquire(); } 
															catch ( DirectInput.InputException ) { } }
				catch ( DirectInput.InputException ) { }

				// add the last frames remaining time to the current elapsed time
				fElapsedTime += m_fRemainder;
				// if enough time remains for another step then process it
				if ( fElapsedTime > m_fStepSize )
				{
					// save the loop var
					float fCurrentTime = 0.0f;
					// update the game until the elapsed time is processed as long as the 
					// remaining unprocessed time is greater than the step size
					for ( ; fCurrentTime < fElapsedTime ; fCurrentTime += m_fStepSize )
					{
						m_oGame.UpdateGame( m_fStepSize );
					}
					// save the unprocessed time for the next frame
					m_fRemainder = fCurrentTime - fElapsedTime;
				}
				else
				{
					// If there wasn't enough time for a single step, just save it all 
					// for the next frame. If this happens a smaller step size should be
					// considered.
					m_fRemainder = fElapsedTime;
				}
				if ( m_oGame.GameOver )
				{
					m_oMainMenu.IsMinimized = false;
					m_oMouse.Unacquire();
				}
			}
        }

        /// <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( Device oDevice , double fAppTime , float fElapsedTime )
        {
            bool bBeginSceneCalled = false;

            // Clear the render target and the zbuffer 
			oDevice.Clear( ClearFlags.ZBuffer | ClearFlags.Target , m_oGame.BackGroundColor , 1.0f , 0 );
            try
            {
                oDevice.BeginScene();
                bBeginSceneCalled = true;

				// setup the device for rendering each frame
				oDevice.RenderState.Lighting = false;
				oDevice.RenderState.CullMode = Cull.None;
				oDevice.Transform.World = Matrix.Identity;

				m_oMainMenu.OnRender( fElapsedTime );
				if ( m_oMainMenu.IsMinimized )
				{
					m_oGame.RenderDisplay( oDevice );
					RenderText();
				}
			}
            finally
            {
				if ( bBeginSceneCalled )
				{
					oDevice.EndScene();
				}
            }
        }

        /// <summary>
        /// Render the help and statistics text. This function uses the Font object for 
        /// efficient text rendering.
        /// </summary>
		private void RenderText()
		{
			TextHelper oTxtHelper = new TextHelper( m_oGameFont , m_oGameTextSprite, 30 );

			// Output game text
			oTxtHelper.Begin();

			// draw remaining balls text

			// drop shadow text
			oTxtHelper.SetInsertionPoint( 32 , 562 );
			oTxtHelper.SetForegroundColor( System.Drawing.Color.FromArgb( 64 , 64 , 64 ) );
			oTxtHelper.DrawTextLine( "Balls: " + m_oGame.PlayerBalls );
			// normal text
			oTxtHelper.SetInsertionPoint( 30 , 560 );
			oTxtHelper.SetForegroundColor( System.Drawing.Color.FromArgb( 192 , 192 , 192 ) );
			oTxtHelper.DrawTextLine( "Balls: " + m_oGame.PlayerBalls );

			// draw score text

			// drop shadow text
			oTxtHelper.SetInsertionPoint( 602 , 562 );
			oTxtHelper.SetForegroundColor( System.Drawing.Color.FromArgb( 64 , 64 , 64 ) );
			oTxtHelper.DrawTextLine( "Score: " + m_oGame.PlayerScore );
			// normal text
			oTxtHelper.SetInsertionPoint( 600 , 560 );
			oTxtHelper.SetForegroundColor( System.Drawing.Color.FromArgb( 192 , 192 , 192 ) );
			oTxtHelper.DrawTextLine( "Score: " + m_oGame.PlayerScore );

			if ( m_bPause )
			{
				// drop shadow text
				oTxtHelper.SetInsertionPoint( 357 , 262 );
				oTxtHelper.SetForegroundColor( System.Drawing.Color.FromArgb( 64 , 64 , 64 ) );
				oTxtHelper.DrawTextLine( "Paused" );
				// normal text
				oTxtHelper.SetInsertionPoint( 355 , 260 );
				oTxtHelper.SetForegroundColor( System.Drawing.Color.FromArgb( 192 , 192 , 192 ) );
				oTxtHelper.DrawTextLine( "Paused" );
			}

			oTxtHelper.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>
        private void OnKeyEvent( System.Windows.Forms.Keys oKey , bool bIsKeyDown , bool bIsKeyUp )
        {
			if ( bIsKeyDown )
			{
				switch( oKey )
				{
					case System.Windows.Forms.Keys.Escape:
						m_oMainMenu.IsMinimized = !m_oMainMenu.IsMinimized;
						if ( !m_oMainMenu.IsMinimized )
						{
							m_oMouse.Unacquire();
						}
						break;
					case System.Windows.Forms.Keys.P:
						m_bPause = !m_bPause;
						if ( m_bPause )
						{
							m_oMouse.Unacquire();
						}
						break;
				}
			}
        }

        /// <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 oMsg , IntPtr wParam , IntPtr lParam , ref bool bNoFurtherProcessing )
        {
			m_oMainMenu.MessageProc( hWnd , oMsg , wParam , lParam );
			return IntPtr.Zero;
		}

        /// <summary>
        /// Initializes the application
        /// </summary>
        public void InitializeApplication()
        {
			// create the input and sound devices

			// only mouse is handled by direct input (so far)
			m_oMouse = new DirectInput.Device( DirectInput.SystemGuid.Mouse );
			m_oMouse.SetCooperativeLevel( m_oFramework.Window , DirectInput.CooperativeLevelFlags.Exclusive | DirectInput.CooperativeLevelFlags.Foreground );

			m_oSound = new DirectSound.Device();
			m_oSound.SetCooperativeLevel( m_oFramework.Window , DirectSound.CooperativeLevel.Normal );

			// let the game init the sounds it needs as soon as we get a device to use em with
			m_oGame.InitializeSound( m_oSound );

			// hook up the disposing event handler (done in the destructor)
			m_oSound.Disposing += new EventHandler( SoundDisposing );
		}

		private void SoundDisposing( object sender , EventArgs e )
		{
			// let the game dispose of it's sounds
			m_oGame.ShutdownSound();
		}

        /// <summary>
        /// Entry point to the program. Initializes everything and goes into a message processing 
        /// loop. Idle time is used to render the scene.
        /// </summary>
        static int Main() 
        {
            using( Framework oFramework = new Framework() )
            {
                NeoBreakOut oNeoBreakOut = new NeoBreakOut( oFramework );
                // Set the callback functions. These functions allow the sample framework to notify
                // the application about device changes, user input, and windows messages.  The 
                // callbacks are optional so you need only set callbacks for events you're interested 
                // in. However, if you don't handle the device reset/lost callbacks then the sample 
                // framework won't be able to reset your device since the application must first 
                // release all device resources before resetting.  Likewise, if you don't handle the 
                // device created/destroyed callbacks then the sample framework won't be able to 
                // recreate your device resources.
                oFramework.Disposing += new EventHandler( oNeoBreakOut.OnDestroyDevice );
                oFramework.DeviceLost += new EventHandler( oNeoBreakOut.OnLostDevice );
                oFramework.DeviceCreated += new DeviceEventHandler( oNeoBreakOut.OnCreateDevice );
                oFramework.DeviceReset += new DeviceEventHandler( oNeoBreakOut.OnResetDevice );

                oFramework.SetKeyboardCallback( new KeyboardCallback( oNeoBreakOut.OnKeyEvent ) );
                oFramework.SetWndProcCallback( new WndProcCallback( oNeoBreakOut.OnMsgProc ) );

                oFramework.SetCallbackInterface( oNeoBreakOut );
                try
                {
                    // Show the cursor and clip it when in full screen
                    oFramework.SetCursorSettings( true , true );


                    // Initialize the sample framework and create the desired window and Direct3D 
                    // device for the application. Calling each of these functions is optional, but they
                    // allow you to set several options which control the behavior of the sampleFramework.
                    oFramework.Initialize( false , false , true ); // Parse the command line, handle the default hotkeys, and show msgboxes
                    oFramework.CreateWindow( "NeoBreakOut" );
					oFramework.CreateDevice( 0 , true , 800 , 600 , oNeoBreakOut );

					// Initialize game, moved initialization to after window creation so it could 
					// be used for other device instantiation)
					oNeoBreakOut.InitializeApplication();

                    // Pass control to the sample framework for handling the message pump and 
                    // dispatching render calls. The sample framework will call your FrameMove 
                    // and FrameRender callback when there is idle time between handling window messages.
                    oFramework.MainLoop();
                }
#if( DEBUG )
                catch ( Exception oException )
                {
                    // In debug mode show this error (maybe - depending on settings)
                    oFramework.DisplayErrorMessage( oException );
#else
            catch
            {
                // In release mode fail silently
#endif
                    // Ignore any exceptions here, they would have been handled by other areas
                    return ( oFramework.ExitCode == 0 ) ? 1 : oFramework.ExitCode; // Return an error code here
                }

                // Perform any application-level cleanup here. Direct3D device resources are released within the
                // appropriate callback functions and therefore don't require any cleanup code here.
                return oFramework.ExitCode;
            }
		}

		// button event handlers

		private void MainMenu_PlayGameButtonClicked(object sender, EventArgs e)
		{
			if ( m_oGame.GameOver )
			{
				m_oGame.Reset();
			}
			m_oMainMenu.IsMinimized = true;
			m_oMainMenu.Refresh();
		}

		private void MainMenu_QuitButtonClicked(object sender, EventArgs e)
		{
			m_oFramework.CloseWindow();
		}

		private void MainMenu_FullscreenToggleButtonClicked(object sender, EventArgs e)
		{
			m_oFramework.ToggleFullscreen();
		}
	}
}
