using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using RecrownedAthenaeum.ContentSystem;
using RecrownedAthenaeum.Input;
using RecrownedAthenaeum.Render;
using RecrownedAthenaeum.UI.SkinSystem;
using System;
using System.Collections.Generic;
using System.Linq;

namespace RecrownedAthenaeum.UI.BookSystem
{
    /// <summary>
    /// Contains the pages.
    /// </summary>
    public class Book : IInputListener
    {
        readonly ContentManagerController assets;
        readonly ISkin skin;
        Page targetPage;
        int width, height;
        Dictionary<string, Page> pages = new Dictionary<string, Page>();
        List<Page> orderedPages = new List<Page>();

        /// <summary>
        /// The camera to use to change between pages.
        /// </summary>
        public Camera2D camera;
        private BasicScissor basicScissor;

        /// <summary>
        /// Creates a book.
        /// </summary>
        /// <param name="assets"><see cref="ContentManagerController"/> that holds the assets that are to be used in the pages for this book during initialization.</param>
        /// <param name="skin">The skin that will be passed to pages during their initialization.</param>
        /// <param name="camera">Camera to move to change pages.</param>
        public Book(ContentManagerController assets, ISkin skin, Camera2D camera)
        {
            this.assets = assets;
            this.skin = skin;

            this.camera = camera ?? throw new ArgumentNullException("Camera can't be null since book needs a camera to move to change between the slides (pages).");
            this.basicScissor = new BasicScissor();
        }

        /// <summary>
        /// Should be called whenever a valid camera and dimensions for the book exist.
        /// Initializes book with given parameters.
        /// Generally used with a <see cref="ScreenSystem.Screen"/> and called in the <see cref="ScreenSystem.Screen.ApplySize(int, int)"/> function.
        /// </summary>
        /// <param name="camera">Camera to move to change pages.</param>
        /// <param name="dimensions">Dimensions of the book and the dimensions the pages will use.</param>
        public void Initiate(Camera2D camera, Rectangle dimensions)
        {
            this.camera = camera;
        }

        /// <summary>
        /// Applies the size if the book's pages.
        /// </summary>
        /// <param name="width">The width of one page.</param>
        /// <param name="height">The height of one page.</param>
        public void ApplySize(int width, int height)
        {
            if (this.width == width && this.height == height) return;

            this.width = width;
            this.height = height;
            for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
            {
                Page page = orderedPages[pageIndex];
                if (page.Boundaries.Width != width || page.Boundaries.Height != height)
                {
                    page.requiresSizeUpdate = true;
                }
            }
        }

        /// <summary>
        /// Draws the pages.
        /// </summary>
        /// <param name="batch">Batch used to draw.</param>
        public void Draw(ConsistentSpriteBatch batch)
        {
            for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
            {
                orderedPages[pageIndex].Draw(batch);
            }
        }

        /// <summary>
        /// Updates the book.
        /// </summary>
        /// <param name="gameTime">Snapshot of information of the game time.</param>
        public void Update(GameTime gameTime)
        {
            if (targetPage != null)
            {
                Vector2 position;
                Rectangle targetBounds = targetPage.Boundaries;
                position.X = targetBounds.X + (targetBounds.Width * 0.5f);
                position.Y = targetBounds.Y + (targetBounds.Height * 0.5f);
                camera.LinearInterpolationToPosition(0.4f, position, (float)gameTime.ElapsedGameTime.TotalSeconds);
                if (camera.Position == position)
                {
                    targetPage = null;
                }
            }

            for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
            {
                Page page = pages.ElementAt(pageIndex).Value;
                if (page.requiresSizeUpdate) page.ApplySize(width, height);
                page.Update(gameTime);
            }
        }

        /// <summary>
        /// Adds the page(s).
        /// </summary>
        /// <param name="pages">The page(s) to add.</param>
        public void AddPages(params Page[] pages)
        {
            foreach (Page page in pages)
            {
                orderedPages.Add(page);
                this.pages.Add(page.name, page);
                page.Initialize(assets, skin, basicScissor);
            }
        }

        /// <summary>
        /// Removes the page.
        /// </summary>
        /// <param name="page">Page to remove.</param>
        public void RemovePage(Page page)
        {
            RemovePage(page.name);
        }

        /// <summary>
        /// Removes the page.
        /// </summary>
        /// <param name="name">Name of page to remove.</param>
        public void RemovePage(string name)
        {
            orderedPages.Remove(pages[name]);
            pages.Remove(name);
        }


        /// <summary>
        /// Removes all pages.
        /// </summary>
        public void ClearPages()
        {
            orderedPages.Clear();
            pages.Clear();
        }

        /// <summary>
        /// Perform a step of linear interpolation to the given page.
        /// </summary>
        /// <param name="page">The page to lerp to.</param>
        public void LerpToPage(Page page)
        {
            targetPage = page;
        }

        /// <summary>
        /// Goes to page instantly.
        /// </summary>
        /// <param name="page">Page to go to.</param>
        public void GoToPage(Page page)
        {
            Rectangle targetBounds = page.Boundaries;
            camera.position.X = targetBounds.X + (targetBounds.Width * 0.5f);
            camera.position.Y = targetBounds.Y + (targetBounds.Height * 0.5f);
        }

        /// <summary>
        /// Passes the new keyboard state down to each page in order of when it was added.
        /// </summary>
        /// <param name="state"></param>
        /// <returns>True if the state change should to trigger further input listeners.</returns>
        public bool KeyboardStateChanged(KeyboardState state)
        {
            for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
            {
                Page page = orderedPages[pageIndex];
                if (!page.KeyboardStateChanged(state)) return false;
            }
            return true;
        }

        /// <summary>
        /// Passes the new mouse state down to each page in order of when it was added.
        /// </summary>
        /// <param name="state"></param>
        /// <returns>True if the state change should to trigger further input listeners.</returns>
        public bool MouseStateChanged(MouseState state)
        {
            for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
            {
                Page page = orderedPages[pageIndex];
                if (!page.MouseStateChanged(state)) return false;
            }
            return true;
        }
    }
}
