using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Threading;
using Sdl;
using StarEngine.Core;

namespace StarEngine.Sdl
{
    /// <summary>
    /// IGameEnvironment の SDL による実装です。
    /// </summary>
    public sealed class GameEnvironment : IGameEnvironment, IDisposable
    {
        /// <summary>
        /// GameEnvironment オブジェクトを生成します。
        /// 同時に二つ生成は出来ません。
        /// </summary>
        /// <param name="windowType">ウィンドウタイプ。</param>
        /// <returns>GameEnvironment オブジェクト。</returns>
        public static GameEnvironment Create(WindowType windowType)
        {
            if (Instance != null && !Instance.IsDisposed)
                throw new InvalidOperationException("GameEnvionment オブジェクトは同時に二つ生成できません。");
            return Instance = new GameEnvironment(windowType);
        }
        private static GameEnvironment Instance;

        private GameEnvironment(WindowType windowType)
        {
            this.WindowType = windowType;

            uint flags =
                SDL.SDL_INIT_VIDEO |
                SDL.SDL_INIT_JOYSTICK |
                SDL.SDL_INIT_AUDIO |
                SDL.SDL_INIT_TIMER;

            if (SDL.SDL_Init(flags) < 0)
                throw new StarEngineException(SDL.SDL_GetError());

            this.SdlScreen = Sdl.Screen.Create(new Size(320, 240), this.WindowType);
            
            this.SdlInput = Sdl.Input.Create();

            this.SdlAudio = Sdl.Audio.Create();

            this.SdlEvent = new SDL.SDL_Event();

            SDL.SDL_ShowCursor((int)SDL.SDL_DISABLE);
        }

        /// <summary>
        /// この GameEnvironment によって使用されているリソースを全て解放します。
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// GameEnvironment のデストラクタです。
        /// </summary>
        ~GameEnvironment()
        {
            this.Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (!this.IsDisposed)
            {
                this.IsDisposed = true;
                if (disposing)
                {
                    this.SdlAudio.Dispose();
                    this.SdlInput.Dispose();
                    this.SdlScreen.Dispose();
                }
                SDL.SDL_Quit();
            }
        }

        /// <summary>
        /// この GameEnvironment が解放されたかどうかを示す値を取得します。
        /// </summary>
        public bool IsDisposed
        {
            get { return this.isDisposed; }
            private set { this.isDisposed = value; }
        }
        private bool isDisposed = false;

        /// <summary>
        /// ウィンドウタイプを取得します。
        /// </summary>
        public WindowType WindowType
        {
            get { return this.windowType; }
            private set { this.windowType = value; }
        }
        private WindowType windowType;

        /// <summary>
        /// 入力機器を取得します。
        /// </summary>
        public IInput Input
        {
            get { return this.SdlInput; }
        }

        private Input SdlInput;

        /// <summary>
        /// 描画対象となる画面を取得します。
        /// </summary>
        public IScreen Screen
        {
            get { return this.SdlScreen; }
        }
        
        private Screen SdlScreen;

        private SDL.SDL_Event SdlEvent;

        /// <summary>
        /// オーディオを取得します。
        /// </summary>
        public IAudio Audio
        {
            get { return this.SdlAudio; }
        }

        private Audio SdlAudio;
        
        /// <summary>
        /// 使用するテクスチャファクトリを取得します。
        /// </summary>
        public ITextureFactory TextureFactory
        {
            get { return StarEngine.Sdl.TextureFactory.Instance; }
        }

        /// <summary>
        /// ゲームを実行します。
        /// </summary>
        /// <param name="game">実行するゲーム。</param>
        public void Run(IGame game)
        {
            this.Title = game.Title;

            if (this.IsRunning) return;
            this.IsRunning = true;
            this.IsTerminated = false;

            int now, nowX;
            int before = Environment.TickCount;
            int before2 = before;
            int errorX = 0;
            int fps = game.Fps;
            

            int tempCounter = 0;
            double frameTime = 1000.0 / game.Fps;

            while (true)
            {
                tempCounter++;

                this.SdlInput.Update(this.SdlScreen);
                if (SDL.SDL_PollEvent(ref this.SdlEvent) != 0 && this.SdlEvent.type == SDL.SDL_QUIT)
                    this.Terminate();
                
                while (100 > (nowX = ((now = Environment.TickCount) - before) * (fps / 10) + errorX))
                    Thread.Sleep(1);
                
                before = now;
                errorX = nowX % 100;

                // per 1 second
                if ((now - before2) >= 1000)
                {
                    this.RealFps = (double)tempCounter / (now - before2) * 1000;
                    Debug.WriteLine(string.Format("FPS: {0:#.00}", this.RealFps));
                    tempCounter = 0;
                    before2 = now;
                }

                game.Update(this);

                this.SdlScreen.Update(game);

                if (this.IsTerminated)
                    break;
            }

            this.IsRunning = false;
        }

        /// <summary>
        /// ゲームが実行中かどうかを示す値を取得します。
        /// </summary>
        public bool IsRunning
        {
            get { return this.isRunning; }
            private set { this.isRunning = value; }
        }
        private bool isRunning = false;

        /// <summary>
        /// ゲームを終了します。
        /// </summary>
        public void Terminate()
        {
            if (this.IsRunning)
                this.IsTerminated = true;
        }

        private bool IsTerminated = false;

        /// <summary>
        /// 実際の FPS を取得します。
        /// </summary>
        public double RealFps
        {
            get { return this.realFps; }
            private set { this.realFps = value; }
        }
        private double realFps;

        /// <summary>
        /// タイトルを設定します。
        /// </summary>
        public string Title
        {
            set
            {
                SDL.SDL_WM_SetCaption(Encoding.UTF8.GetBytes(value), null);
            }
        }
    }
}
