using System;
using System.Collections.Generic;
using Encompass;
using Kav;
using KavTest.Components;
using KavTest.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace KavTest.Renderers
{
    public class SceneRenderer : GeneralRenderer
    {
        private GraphicsDevice GraphicsDevice { get; }
        private SpriteBatch SpriteBatch { get; }
        private InstancedModelContainer InstancedModelContainer { get; }

        private Kav.Renderer Renderer { get; }

        private RenderTargetBinding[] GBuffer { get; }

        private RenderTarget2D GPosition { get; }
        private RenderTarget2D GNormal { get; }
        private RenderTarget2D GAlbedo { get; }
        private RenderTarget2D GMetallicRoughness { get; }

        private RenderTarget2D DeferredTarget { get; }
        private RenderTarget2D BillboardTarget { get; }
        private RenderTargetCube ShadowCubeMap { get; }
        private DirectionalShadowMapData DirectionalShadowMapData { get; }

        private readonly Dictionary<Kav.MeshPart, List<Matrix>> InstanceMap = new Dictionary<MeshPart, List<Matrix>>();

        private IEnumerable<Matrix> CubeTransforms
        {
            get
            {
                foreach (var entity in ReadEntitiesAsEnumerable<InstancedCubeComponent>())
                {
                    if (HasComponent<Transform3DComponent>(entity))
                    {
                        var transformComponent = GetComponent<Transform3DComponent>(entity);

                        yield return transformComponent.Transform.TransformMatrix;
                    }
                }
            }
        }

        private IEnumerable<(Kav.MeshPart, Matrix)> MeshPartTransforms
        {
            get
            {
                foreach (var entity in ReadEntitiesAsEnumerable<ModelComponent>())
                {
                    /* FIXME: this transformation should definitely not go here */
                    var transformComponent = GetComponent<Transform3DComponent>(entity);
                    var modelComponent = GetComponent<ModelComponent>(entity);

                    foreach (var mesh in modelComponent.Model.Meshes)
                    {
                        foreach (var meshPart in mesh.MeshParts)
                        {
                            if (HasComponent<OverrideAlbedoComponent>(entity))
                            {
                                var overrideAlbedoComponent = GetComponent<OverrideAlbedoComponent>(entity);

                                meshPart.DisableAlbedoMap = true;
                                meshPart.Albedo = overrideAlbedoComponent.Color;
                            }

                            yield return (meshPart, transformComponent.Transform.TransformMatrix);
                        }
                    }
                }
            }
        }

        private AmbientLight AmbientLight
        {
            get
            {
                if (SomeComponent<AmbientLightComponent>())
                {
                    return new AmbientLight(ReadComponent<AmbientLightComponent>().Color);
                }
                else
                {
                    return new AmbientLight(Color.Black);
                }
            }
        }

        private IEnumerable<PointLight> PointLights
        {
            get
            {
                foreach (var entity in ReadEntitiesAsEnumerable<PointLightComponent>())
                {
                    var transformComponent = GetComponent<Transform3DComponent>(entity);
                    var pointLightComponent = GetComponent<PointLightComponent>(entity);

                    yield return new PointLight(
                        transformComponent.Transform.Position,
                        pointLightComponent.Color,
                        pointLightComponent.Intensity
                    );
                }
            }
        }

        private Kav.DirectionalLight? DirectionalLight()
        {
            if (SomeComponent<DirectionalLightComponent>())
            {
                var entity = ReadEntity<DirectionalLightComponent>();
                var transformComponent = GetComponent<Transform3DComponent>(entity);
                var directionalLightComponent = GetComponent<DirectionalLightComponent>(entity);

                return new Kav.DirectionalLight(
                    transformComponent.Transform.Forward,
                    directionalLightComponent.Color,
                    directionalLightComponent.Intensity
                );
            }
            else
            {
                return null;
            }
        }

        private IEnumerable<(MeshSprite, Matrix)> MeshSpriteTransforms()
        {
            foreach (var entity in ReadEntitiesAsEnumerable<MeshSpriteComponent>())
            {
                var transformComponent = GetComponent<Transform3DComponent>(entity);
                var spriteComponent = GetComponent<MeshSpriteComponent>(entity);

                yield return (spriteComponent.MeshSprite, transformComponent.Transform.TransformMatrix);
            }
        }

        public SceneRenderer(
            GraphicsDevice graphicsDevice,
            InstancedModelContainer instancedModelContainer
        ) {
            GraphicsDevice = graphicsDevice;
            InstancedModelContainer = instancedModelContainer;

            var renderDimensionsX = GraphicsDevice.PresentationParameters.BackBufferWidth;
            var renderDimensionsY = GraphicsDevice.PresentationParameters.BackBufferHeight;

            Renderer = new Kav.Renderer(
                GraphicsDevice
            );

            DeferredTarget = new RenderTarget2D(
                GraphicsDevice,
                renderDimensionsX,
                renderDimensionsY,
                false,
                SurfaceFormat.Color,
                DepthFormat.Depth24Stencil8,
                0,
                RenderTargetUsage.PreserveContents
            );

            BillboardTarget = new RenderTarget2D(
                GraphicsDevice,
                renderDimensionsX,
                renderDimensionsY,
                false,
                SurfaceFormat.Color,
                DepthFormat.Depth24Stencil8,
                0,
                RenderTargetUsage.PreserveContents
            );

            GPosition = new RenderTarget2D(
                GraphicsDevice,
                renderDimensionsX,
                renderDimensionsY,
                false,
                SurfaceFormat.Vector4,
                DepthFormat.Depth24,
                0,
                RenderTargetUsage.PreserveContents
            );

            GNormal = new RenderTarget2D(
                GraphicsDevice,
                renderDimensionsX,
                renderDimensionsY,
                false,
                SurfaceFormat.Vector4,
                DepthFormat.None
            );

            GAlbedo = new RenderTarget2D(
                GraphicsDevice,
                renderDimensionsX,
                renderDimensionsY,
                false,
                SurfaceFormat.Color,
                DepthFormat.None
            );

            GMetallicRoughness = new RenderTarget2D(
                GraphicsDevice,
                renderDimensionsX,
                renderDimensionsY,
                false,
                SurfaceFormat.HalfVector2,
                DepthFormat.None
            );

            GBuffer = new RenderTargetBinding[4] {
                new RenderTargetBinding(GPosition),
                new RenderTargetBinding(GNormal),
                new RenderTargetBinding(GAlbedo),
                new RenderTargetBinding(GMetallicRoughness)
            };

            ShadowCubeMap = Kav.Renderer.CreateShadowCubeMap(
                GraphicsDevice,
                2048
            );

            DirectionalShadowMapData = Kav.Renderer.CreateDirectionalShadowMaps(
                graphicsDevice,
                2048,
                4
            );

            SpriteBatch = new SpriteBatch(GraphicsDevice);
        }

        public override void Render()
        {
            if (SomeComponent<PerspectiveCameraComponent>())
            {
                var cameraEntity = ReadEntity<PerspectiveCameraComponent>();
                var transformComponent = GetComponent<ArcballTransformComponent>(cameraEntity);
                var cameraComponent = GetComponent<PerspectiveCameraComponent>(cameraEntity);

                var camera = new Kav.PerspectiveCamera(
                    transformComponent.ArcballTransform.Position,
                    transformComponent.ArcballTransform.Forward,
                    transformComponent.ArcballTransform.Up,
                    cameraComponent.FieldOfView,
                    cameraComponent.AspectRatio,
                    cameraComponent.NearPlane,
                    cameraComponent.FarPlane
                );

                GraphicsDevice.SetRenderTargets(GBuffer);
                GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0);

                GraphicsDevice.SetRenderTarget(DeferredTarget);
                GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0);
                GraphicsDevice.DepthStencilState = DepthStencilState.Default;

                // Renderer.GBufferRender(
                //     GBuffer,
                //     camera,
                //     ModelTransforms
                // );

                Renderer.RenderGBufferInstanced(
                    GBuffer,
                    DeferredTarget,
                    camera,
                    InstancedModelContainer.CubeModel,
                    CubeTransforms
                );

                Renderer.RenderGBufferIndexed(
                    GBuffer,
                    camera,
                    MeshPartTransforms
                );

                Renderer.RenderDepthIndexed(
                    DeferredTarget,
                    camera,
                    MeshPartTransforms
                );

                Renderer.RenderAmbientLight(
                    DeferredTarget,
                    GPosition,
                    GAlbedo,
                    AmbientLight
                );

                foreach (var pointLight in PointLights)
                {
                    foreach (CubeMapFace face in Enum.GetValues(typeof(CubeMapFace)))
                    {
                        GraphicsDevice.SetRenderTarget(ShadowCubeMap, face);
                        GraphicsDevice.Clear(Color.White);
                    }

                    Renderer.RenderPointShadowsIndexed(
                        ShadowCubeMap,
                        camera,
                        MeshPartTransforms,
                        pointLight
                    );

                    Renderer.RenderPointLight(
                        DeferredTarget,
                        GPosition,
                        GAlbedo,
                        GNormal,
                        GMetallicRoughness,
                        ShadowCubeMap,
                        camera,
                        pointLight
                    );
                }

                var directionalLight = DirectionalLight();

                if (directionalLight.HasValue)
                {
                    DirectionalShadowMapData.Clear(GraphicsDevice);

                    Renderer.RenderDirectionalShadowsIndexed(
                        DirectionalShadowMapData,
                        camera,
                        MeshPartTransforms,
                        directionalLight.Value
                    );

                    Renderer.RenderDirectionalLightToon(
                        DeferredTarget,
                        GPosition,
                        GAlbedo,
                        GNormal,
                        GMetallicRoughness,
                        DirectionalShadowMapData,
                        camera,
                        MeshPartTransforms,
                        directionalLight.Value,
                        false
                    );
                }

                Renderer.RenderSkybox(
                    DeferredTarget,
                    camera,
                    ReadComponent<SkyboxComponent>().Skybox
                );

                Renderer.MeshSpriteRender(
                    DeferredTarget,
                    camera,
                    MeshSpriteTransforms(),
                    AmbientLight,
                    PointLights,
                    DirectionalLight()
                );

                GraphicsDevice.SetRenderTarget(null);
                SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null);
                SpriteBatch.Draw(DeferredTarget, Vector2.Zero, Color.White);
                //SpriteBatch.Draw(BillboardTarget, Vector2.Zero, Color.White);
                SpriteBatch.End();
            }
        }

        private void CollectInstances()
        {
            foreach (var list in InstanceMap.Values)
            {
                list.Clear();
            }

            foreach (var modelEntity in ReadEntities<ModelComponent>())
            {
                if (HasComponent<Transform3DComponent>(modelEntity))
                {
                    var modelComponent = GetComponent<ModelComponent>(modelEntity);
                    if (modelComponent.Instanced)
                    {
                        var transformComponent = GetComponent<Transform3DComponent>(modelEntity);

                        foreach (var mesh in modelComponent.Model.Meshes)
                        {
                            foreach (var meshPart in mesh.MeshParts)
                            {
                                if (!InstanceMap.ContainsKey(meshPart))
                                {
                                    InstanceMap.Add(meshPart, new List<Matrix>());
                                }
                                else

                                InstanceMap[meshPart].Add(transformComponent.Transform.TransformMatrix);
                            }
                        }
                    }
                }
            }
        }
    }
}
