#region File Description
//-----------------------------------------------------------------------------
// GraphicsDeviceService.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Threading;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.Windows.Forms;
#endregion

// IGraphicsDeviceService インターフェイスは、DeviceCreated イベントを必要としますが、
// 常にコンストラクター内部でデバイスを作成するだけなので、そのイベントを発生させる
// 場所がありません。C# コンパイラから、イベントが使用されていない、と警告されますが、
// ここでは心配ないので、単にこの警告を無効にします。
#pragma warning disable 67

namespace WinFormsGraphicsDevice
{
    /// <summary>
    /// GraphicsDevice の作成と管理を担当するヘルパー クラス。
    /// すべての GraphicsDeviceControl インスタンスは、同一の GraphicsDeviceService を
    /// 共有するので、多数のコントロールが存在する場合でも、背後の GraphicsDevice は 
    /// 1 つしか存在しません。このヘルパーは標準の IGraphicsDeviceService 
    /// インターフェイスを実装します。このインターフェイスは、デバイスがリセットまたは
    /// 破棄されたときにそれに関する通知イベントを生成します。
    /// </summary>
    class GraphicsDeviceService : IGraphicsDeviceService
    {
        #region Fields


        // シングルトン デバイス サービス インスタンス。
        static GraphicsDeviceService singletonInstance;


        // singletonInstance を共有しているコントロールの数を追跡します。
        static int referenceCount;

        static ServiceContainer services = new ServiceContainer();

        #endregion


        /// <summary>
        /// シングルトン クラスなのでコンストラクターはプライベートです。
        /// クライアント コントロールは、代わりにパブリックの AddRef メソッドを
        /// 使用する必要があります。
        /// </summary>
        GraphicsDeviceService(IntPtr windowHandle, int width, int height)
        {
            parameters = new PresentationParameters();

            parameters.BackBufferWidth = Math.Max(width, 1);
            parameters.BackBufferHeight = Math.Max(height, 1);
            parameters.BackBufferFormat = SurfaceFormat.Color;
            parameters.DepthStencilFormat = DepthFormat.Depth24;
            parameters.DeviceWindowHandle = windowHandle;
            parameters.PresentationInterval = PresentInterval.Immediate;
            //parameters.RenderTargetUsage = RenderTargetUsage.DiscardContents;
            parameters.IsFullScreen = false;

            graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter,
                                                GraphicsProfile.HiDef,
                                                parameters);
            InitGraphicsDevice(graphicsDevice);
        }

        private void InitGraphicsDevice(GraphicsDevice dev) {
            // ぼやけなくする: disable texture smoothing
            dev.SamplerStates[0] = SamplerState.PointClamp;
            // 深度バッファ有効: enable depth buffer
            dev.DepthStencilState = DepthStencilState.Default;
        }

        /// <summary>
        /// シングルトン インスタンスに対する参照を取得します。
        /// </summary>
        public static GraphicsDeviceService AddRef(IntPtr windowHandle,
                                                   int width, int height)
        {
            // "デバイスを共有しているコントロールの数" カウンターを増やします。
            if (Interlocked.Increment(ref referenceCount) == 1)
            {                
                if (windowHandle == IntPtr.Zero) {
                    int i = 0;
                    int c = Application.OpenForms.Count;
                    while (c > 0) {
                        windowHandle = Application.OpenForms[i++].Handle;
                        c--;
                        if (windowHandle != IntPtr.Zero)
                            break;
                    }
                }
                // これが、デバイスを使用し始める最初のコントロールの場合、
                // シングルトン インスタンスを作成する必要があります。
                singletonInstance = new GraphicsDeviceService(windowHandle,
                                                              width, height);

                // ContentManager などのコンポーネントから検出できるように、
                // サービスを登録します。
                // ※GraphicDeviceContorlにあった処理をこちらに移動 c477
                services.AddService<IGraphicsDeviceService>(singletonInstance);
            }

            return singletonInstance;
        }

        /// <summary>
        /// このサンプルの IGraphicsDeviceService を含む IServiceProvider を取得します。
        /// これは、ContentManager などのコンポーネントで使用できます。これらの
        /// コンポーネントはこのサービスを使用して、GraphicsDevice を取得します。
        /// ※GraphicDeviceContorlにあったメソッドをこちらに移動 c477
        /// </summary>
        public static ServiceContainer Services {
            get { return services; }
        }

        /// <summary>
        /// シングルトン インスタンスに対する参照を解放します。
        /// </summary>
        public void Release(bool disposing)
        {
            // "デバイスを共有するコントロール数" カウンターを減らします。
            if (Interlocked.Decrement(ref referenceCount) == 0)
            {
                // これが、デバイスを使用し終える最後のコントロールの場合、
                // シングルトン インスタンスを破棄する必要があります。
                if (disposing)
                {
                    if (DeviceDisposing != null)
                        DeviceDisposing(this, EventArgs.Empty);

                    graphicsDevice.Dispose();
                }

                graphicsDevice = null;
            }
        }

        /// <summary>
        /// BeginDraw により使用されるヘルパー。これは、グラフィック デバイスのステータスを
        /// チェックして、現在のコントロールの描画に十分大きく、デバイスがロストしていない
        /// ことを確認します。デバイスをリセットできなかった場合、エラー文字列を返します。
        /// ※GraphicDeviceContorlにあったメソッドをこちらに移動 c477
        /// </summary>
        public bool PrepareDrawing(int width, int height, out Viewport viewport) 
        {
            bool deviceNeedsReset = false;

            switch (graphicsDevice.GraphicsDeviceStatus)
            {
                case GraphicsDeviceStatus.Lost:
                    // グラフィック デバイスがロストしている場合は、まったく使用できません。
                    throw new IOException("Graphics device lost");

                case GraphicsDeviceStatus.NotReset:
                    // デバイスがリセットされていない状態の場合は、リセットを試みる必要があります。
                    deviceNeedsReset = true;
                    break;

                default:
                    // デバイスの状態が問題ない場合、十分に大きいかどうかをチェックします。
                    PresentationParameters pp = graphicsDevice.PresentationParameters;

                    deviceNeedsReset = (width > pp.BackBufferWidth) || (height > pp.BackBufferHeight);
                    break;
            }

            // デバイスをリセットする必要がありますか。
            if (deviceNeedsReset)
            {
                try
                {
                    ResetDevice(width, height);
                }
                catch (Exception e)
                {
                    throw new IOException("Graphics device reset failed",e);
                }
            }

            // 多数の GraphicsDeviceControl インスタンスが、同一の GraphicsDevice を
            // 共有できます。デバイス バックバッファーは、これらのうち最大のコントロール
            // に合わせてサイズが変更されます。では、現在より小さなコントロールを
            // 描画している場合はどうでしょうか。不要に引き伸ばされることを避けるため、
            // フル バックバッファーの左上部分だけを使用するように、ビューポートを設定します。
            viewport = new Viewport();

            viewport.X = 0;
            viewport.Y = 0;

            viewport.Width = width;
            viewport.Height = height;

            viewport.MinDepth = 0;
            viewport.MaxDepth = 1;

            //graphicsDevice.Viewport = viewport;
            
            return deviceNeedsReset;
        }

        /// <summary>
        /// グラフィック デバイスを、指定した解像度または現在保有している
        /// コントロール サイズの大きい方に合わせてリセットします。この動作は、
        /// デバイスが、すべての GraphicsDeviceControl クライアントのうち
        /// 最大のものに応じて大きくなることを意味します。
        /// </summary>
        public void ResetDevice(int width, int height)
        {
            if (DeviceResetting != null)
                DeviceResetting(this, EventArgs.Empty);

            parameters.BackBufferWidth = Math.Max(parameters.BackBufferWidth, width);
            parameters.BackBufferHeight = Math.Max(parameters.BackBufferHeight, height);

            graphicsDevice.Reset(parameters);

            if (DeviceReset != null)
                DeviceReset(this, EventArgs.Empty);
        }

        
        /// <summary>
        /// 現在のグラフィック デバイスを取得します。
        /// </summary>
        public GraphicsDevice GraphicsDevice
        {
            get { return graphicsDevice; }
        }

        GraphicsDevice graphicsDevice;


        // 現在のデバイス設定を格納します。
        PresentationParameters parameters;


        // IGraphicsDeviceService イベント。
        public event EventHandler<EventArgs> DeviceCreated;
        public event EventHandler<EventArgs> DeviceDisposing;
        public event EventHandler<EventArgs> DeviceReset;
        public event EventHandler<EventArgs> DeviceResetting;
    }
}
