using System;
using System.Collections.Generic;
using System.IO;
using SFML.Graphics;
using SFML.Window;
using YourBeatingHeart.Main.Sound;
using YourBeatingHeart.Main.States;
using YourBeatingHeart.Main.States.Play;
using View = SFML.Graphics.View;

namespace YourBeatingHeart.Main
{
    public class Game
    {
        public RenderWindow Window { get; protected set; }
        private GameState _currentGameState;
        private GameState nextGameState;

        private Dictionary<String, Texture> cacheTextures;
        private Dictionary<String, Font> cacheFonts;

        private View viewPixelPerfect;
        private View viewUpscaled;

        public Random random;

        private bool _upscaled;

        private List<uint> connectedJoystickIds; 

        public bool Upscalable
        {
            get { return viewUpscaled != null; }
        }

        public bool Upscaled
        {
            get { return _upscaled && Upscalable; }
            set
            {
                // Not upscalable? Then just set set the window, not the value - it doesn't need to be saved.
                if (!Upscalable)
                {
                    value = false;
                    Window.SetView(value ? viewUpscaled : viewPixelPerfect);
                    return;
                }

                _upscaled = value;
                Window.SetView(_upscaled ? viewUpscaled : viewPixelPerfect);
            }
        }

        public Game()
        {
            random = new Random();

            bool fullscreen = true;
            bool canUpscale = true;
            if (Constants.DEBUG)
            {
                fullscreen = Constants.DEBUG_FULLSCREEN;
            }

            ContextSettings contextSettings = new ContextSettings();
            contextSettings.AntialiasingLevel = 4;

            // Create the main window
            if (fullscreen)
            {
                // TODO correct fullscreen scaling

                Styles style = Styles.Fullscreen;
                VideoMode desktopMode = VideoMode.DesktopMode;
                uint fullscreenWidth = desktopMode.Width;
                uint fullscreenHeight = desktopMode.Height;

                if (Constants.DEBUG_FULLSCREEN_WINDOW)
                {
                    style = Styles.Default;
                    //fullscreenWidth = 1920;
                    //fullscreenHeight = 768;
                }

                Window = new RenderWindow(new VideoMode(fullscreenWidth, fullscreenHeight), Constants.WINDOW_NAME, style, contextSettings);

                float targetAspectRatio = (float)Constants.TARGET_WIDTH / Constants.TARGET_HEIGHT;
                float currentAspectRatio = (float)fullscreenWidth / fullscreenHeight;

                /*
                // Screen to small? Then we will just scale regardless of what would've been the preference.
                if ((fullscreenWidth <= Constants.TARGET_WIDTH) || (fullscreenHeight <= Constants.TARGET_HEIGHT))
                {
                    canUpscale = false;
                }
                 */

                if (canUpscale)
                {
                    float viewWidth = Constants.TARGET_WIDTH;
                    float viewHeight = Constants.TARGET_HEIGHT;

                    // Keep aspect ratio
                    if (currentAspectRatio > targetAspectRatio)
                    {
                        viewWidth = viewHeight * currentAspectRatio;
                    }
                    else
                    {
                        viewHeight = viewWidth / currentAspectRatio;
                    }

                    viewUpscaled = new View(new FloatRect((int)((Constants.TARGET_WIDTH - viewWidth) / 2), (int)((Constants.TARGET_HEIGHT - viewHeight) / 2), viewWidth, viewHeight));
                }

                viewPixelPerfect = new View(new FloatRect((int)(((int)Constants.TARGET_WIDTH - (int)fullscreenWidth) / 2), (int)(((int)Constants.TARGET_HEIGHT - (int)fullscreenHeight) / 2),
                    fullscreenWidth, fullscreenHeight));

                // Center
                //viewRect.Left = 0; viewRect.Top = 0;

                Upscaled = Constants.FULLSCREEN_DEFAULT_UPSCALED;
            }
            else
            {
                // Windowed
                Window = new RenderWindow(new VideoMode(Constants.TARGET_WIDTH, Constants.TARGET_HEIGHT), Constants.WINDOW_NAME, Styles.Close, contextSettings);
            }
            Window.Closed += OnClose;

            connectedJoystickIds = new List<uint>();
            Window.JoystickConnected += WindowOnJoystickConnected;
            Window.JoystickDisconnected += WindowOnJoystickDisconnected;

            Window.SetVerticalSyncEnabled(true);
            Window.SetKeyRepeatEnabled(false);
            Window.SetMouseCursorVisible(false);

            cacheTextures = new Dictionary<string, Texture>();
            cacheFonts = new Dictionary<string, Font>();

            SoundManager.Instance.PreloadSounds();

            if (Constants.DEBUG)
            {
                CurrentGameState = new PlayState(this, 4);
            }
            else
            {
                CurrentGameState = new TitleScreenState(this);
            }
        }

        public void Run()
        {
            if (Window == null)
                return;

            System.Diagnostics.Stopwatch clock = new System.Diagnostics.Stopwatch();
            // Start the Game loop
            while (Window.IsOpen())
            {

                // Switch states if requested 
                if (nextGameState != null)
                {
                    CurrentGameState = nextGameState;
                    nextGameState = null;
                }

                // Process events
                Window.DispatchEvents();

                float elapsedTimeInS = (float) clock.Elapsed.TotalSeconds;
                clock.Restart();

                CurrentGameState.Update(elapsedTimeInS);

                // Clear screen
                Window.Clear();

                CurrentGameState.Draw();

                // Update the window
                Window.Display();
            }
        }

        /// <summary>
        /// Switch to another Game state. This will be postponed until the current
        /// Game state is rendered and displayed.
        /// </summary>
        /// <param name="gameState">The next GameState</param>
        public void SwitchToGameState(GameState gameState)
        {
            nextGameState = gameState;
        }

        private GameState CurrentGameState
        {
            get { return _currentGameState; }

            set
            {
                if (_currentGameState != null)
                {
                    _currentGameState.TearDown();
                }

                _currentGameState = value;

                if (_currentGameState != null)
                {
                    _currentGameState.SetUp();
                }
            }
        }

        protected void OnClose(object sender, EventArgs e)
        {
            // Close the window when OnClose event is received
            //RenderWindow window = (RenderWindow)sender;
            Window.Close();
        }

        private void WindowOnJoystickConnected(object sender, JoystickConnectEventArgs eventArgs)
        {
            connectedJoystickIds.Add(eventArgs.JoystickId);
        }

        private void WindowOnJoystickDisconnected(object sender, JoystickConnectEventArgs eventArgs)
        {
            connectedJoystickIds.Remove(eventArgs.JoystickId);
        }

        public bool IsJoystickConnected(uint id)
        {
            return connectedJoystickIds.Contains(id);
        }

        /// <summary>
        /// Loads an image and uses a cache on subsequent calls.
        /// </summary>
        /// <param name="path">Path to the image, without "data\\images\\"</param>
        /// <returns>The requested Texture</returns>
        public Texture LoadTexture(String path)
        {
            if (cacheTextures.ContainsKey(path))
            {
                return cacheTextures[path];
            }

            Texture texture = new Texture(Path.Combine(Constants.FOLDER_IMAGES, path));
            cacheTextures[path] = texture;
            return texture;
        }

        /// <summary>
        /// Loads a font and uses a cache on subsequent calls.
        /// </summary>
        /// <param name="path">Path to the font, without "data\\fonts\\"</param>
        /// <returns>The requested Font</returns>
        public Font LoadFont(String path)
        {
            if (cacheFonts.ContainsKey(path))
            {
                return cacheFonts[path];
            }

            Font font = new Font(Path.Combine(Constants.FOLDER_FONTS, path));
            cacheFonts[path] = font;
            return font;
        }

        //public float WindowWidth { get { return Window.Size.X; }}
        //public float WindowHeight { get { return Window.Size.Y; } }

        public float Random(float minInclusive, float maxExclusive)
        {
            return minInclusive + (float)random.NextDouble() * (maxExclusive - minInclusive);
        }

        public int Random(int maxExclusive)
        {
            return random.Next(maxExclusive);
        }

        public int Random(int minInclusive, int maxExclusive)
        {
            return random.Next(minInclusive, maxExclusive);
        }
    }
}
