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
{
    public sealed class GameEnvironment : IGameEnvironment, IDisposable
    {
        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);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~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();
            }
        }

        public bool IsDisposed
        {
            get { return this.isDisposed; }
            private set { this.isDisposed = value; }
        }
        private bool isDisposed = false;

        public WindowType WindowType
        {
            get { return this.windowType; }
            private set { this.windowType = value; }
        }
        private WindowType windowType;

        public IInput Input
        {
            get { return this.SdlInput; }
        }

        private Input SdlInput;

        public IScreen Screen
        {
            get { return this.SdlScreen; }
        }
        
        private Screen SdlScreen;

        private SDL.SDL_Event SdlEvent;

        public IAudio Audio
        {
            get { return this.SdlAudio; }
        }

        private Audio SdlAudio;
        
        public ITextureFactory TextureFactory
        {
            get { return StarEngine.Sdl.TextureFactory.Instance; }
        }

        public void Run(IGame game)
        {
            this.Title = game.Title;

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

            uint before = SDL.SDL_GetTicks();
            uint before2 = before;
            uint interval;
            bool renderSkipping = false;
            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();

                interval = SDL.SDL_GetTicks() - before;
                if (interval < frameTime)
                    Thread.Sleep((int)(frameTime - interval));
                renderSkipping = (frameTime * 2 <= interval);

                before = SDL.SDL_GetTicks();

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

                game.Update(this);

                if (!renderSkipping)
                    this.SdlScreen.Update(game);

                if (this.IsTerminated)
                    break;
            }

            this.IsRunning = false;
        }

        public bool IsRunning
        {
            get { return this.isRunning; }
            private set { this.isRunning = value; }
        }
        private bool isRunning = false;

        public void Terminate()
        {
            if (this.IsRunning)
                this.IsTerminated = true;
        }

        private bool IsTerminated = false;

        public double RealFps
        {
            get { return this.realFps; }
            private set { this.realFps = value; }
        }
        private double realFps;

        public string Title
        {
            set
            {
                SDL.SDL_WM_SetCaption(Encoding.UTF8.GetBytes(value), null);
            }
        }
    }
}
