using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;
using Sdl;
using StarEngine.Core;

namespace StarEngine.Sdl
{
    /// <summary>
    /// 画面を表します。
    /// </summary>
    internal sealed class Screen : IScreen, IDisposable
    {
        internal const int Bpp = 32;

        /// <summary>
        /// 新しい Screen オブジェクトを生成します。
        /// </summary>
        /// <param name="size">画面の大きさ。</param>
        /// <param name="windowType">ウィンドウの種類。</param>
        /// <returns>新しい Screen。</returns>
        public static Screen Create(Size size, WindowType windowType)
        {
            if (Instance != null && !Instance.IsDisposed)
                throw new InvalidOperationException("Screen オブジェクトは同時に二つ生成できません。");
            return Instance = new Screen(size, windowType);
        }

        private static Screen Instance;

        private Screen(Size size, WindowType windowType)
        {
            this.size = size;
            this.windowType = windowType;

            this.SetVideoMode();

            SDL.SDL_WM_SetCaption(Encoding.Default.GetBytes(""), null);
        }

        private void SetVideoMode()
        {
            uint options = 0;
            options |= SDL.SDL_HWACCEL;
            options |= SDL.SDL_DOUBLEBUF;

            if (this.WindowType == WindowType.FullScreen)
                options |= SDL.SDL_FULLSCREEN;

            this.Surface = SDL.SDL_SetVideoMode(
                (int)this.WindowSize.Width, (int)this.WindowSize.Height,
                Bpp, options);
            if (this.Surface == IntPtr.Zero)
                throw new StarEngineException("Video の初期化に失敗しました。");
        }

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

        ~Screen()
        {
            this.Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (!this.IsDisposed)
            {
                this.IsDisposed = true;
                SDL.SDL_FreeSurface(this.Surface);
            }
        }

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

        public Size Size
        {
            get { return this.size; }
            private set { this.size = value; }
        }
        private Size size;

        public int Width
        {
            get { return this.Size.Width; }
        }

        public int Height
        {
            get { return this.Size.Height; }
        }

        /// <summary>
        /// 画面上の座標を論理座標に変換します。
        /// </summary>
        internal Point ToLogicalLocation(Point point)
        {
            switch (this.WindowType)
            {
            case WindowType.DoubleSizeWindow:
            case WindowType.FullScreen:
                return new Point(point.X / 2, point.Y / 2);
            default:
                return point;
            }
        }

        /// <summary>
        /// 画面のフォームのサイズを取得します。
        /// </summary>
        internal Size WindowSize
        {
            get
            {
                switch (this.WindowType)
                {
                case WindowType.DoubleSizeWindow:
                case WindowType.FullScreen:
                    return new Size(this.Size.Width * 2, this.Size.Height * 2);
                default:
                    return Size;
                }
            }
        }

        internal WindowType WindowType
        {
            get { return this.windowType; }
            set
            {
                this.windowType = value;
                this.SetVideoMode();
            }
        }
        private WindowType windowType;

        public ITexture Offscreen
        {
            get
            {
                if (this.offscreen == null)
                    this.offscreen = new Texture(this.Size);
                return this.offscreen;
            }
        }
        private ITexture offscreen;

        private bool ToSaveScreenshot = false;
        private string ScreenshotPath;
        private ImageFormat ScreenshotImageFormat;

        public void SaveScreenshot(string path, ImageFormat imageFormat)
        {
            this.ToSaveScreenshot = true;
            this.ScreenshotPath = path;
            this.ScreenshotImageFormat = imageFormat;
        }

        private IntPtr Surface
        {
            get { return surface; }
            set { surface = value; }
        }
        private IntPtr surface;

        internal void Update(IGame game)
        {
            if (this.ToSaveScreenshot)
            {
                this.ToSaveScreenshot = false;
                using (Bitmap bitmap = this.Offscreen.ToBitmap())
                    bitmap.Save(this.ScreenshotPath, this.ScreenshotImageFormat);
            }

            SDL.SDL_LockSurface(this.Surface);
            unsafe
            {
                uint[] pSrc = ((Texture)this.Offscreen).Pixels;
                uint* pDst = (uint*)((SDL.SDL_Surface*)this.Surface)->pixels;

                switch (this.WindowType)
                {
                case WindowType.DoubleSizeWindow:
                case WindowType.FullScreen:
                    int width = this.Width;
                    int width2 = width * 2;
                    int height = this.Height;
                    int iSrc = 0;
                    for (int j = 0; j < height; j++)
                    {
                        for (int i = 0; i < width; i++)
                        {
                            *pDst = pDst[width2] = pSrc[iSrc];
                            pDst++;
                            *pDst = pDst[width2] = pSrc[iSrc];
                            pDst++;
                            iSrc++;
                        }
                        pDst += width2;
                    }
                    break;
                default:
                    for (int i = 0; i < pSrc.Length; i++)
                        *pDst++ = pSrc[i];
                    break;
                }
            }
            SDL.SDL_UnlockSurface(this.Surface);

            SDL.SDL_Flip(this.Surface);
        }

        
    }
}
