﻿using System;
using System.Collections.Generic;
using Oni.Imaging;

namespace Oni.Akira
{
    internal class MaterialLibrary
    {
        private readonly MarkerMaterials markers = new MarkerMaterials();
        private readonly Dictionary<string, Material> materials = new Dictionary<string, Material>(StringComparer.OrdinalIgnoreCase);
        private Material notFound;

        #region internal class MarkerMaterials

        internal class MarkerMaterials
        {
            private Material ghost;
            private Material stairs;
            private Material door;
            private Material danger;
            private Material barrier;
            private Material impassable;
            private Material blackness;
            private Material floor;

            public Material GetMarker(string name)
            {
                EnsureMaterials();

                switch (name)
                {
                    case "_marker_door":
                        return door;
                    case "_marker_ghost":
                        return ghost;
                    case "_marker_stairs":
                        return stairs;
                    case "_marker_danger":
                        return danger;
                    case "_marker_barrier":
                        return barrier;
                    case "_marker_impassable":
                        return impassable;
                    case "_marker_blackness":
                        return blackness;
                    case "_marker_floor":
                        return floor;
                    default:
                        return null;
                }
            }

            public Material GetMarker(Polygon polygon)
            {
                EnsureMaterials();

                var flags = polygon.Flags & ~(GunkFlags.ProjectionBit0 | GunkFlags.ProjectionBit1 | GunkFlags.Horizontal | GunkFlags.Vertical);
                var adjacencyFlags = GunkFlags.Ghost | GunkFlags.StairsUp | GunkFlags.StairsDown;

                if ((flags & (GunkFlags.DoorFrame | adjacencyFlags)) == GunkFlags.DoorFrame)
                    return door;

                if ((flags & adjacencyFlags) != 0)
                    return ghost;

                if ((flags & GunkFlags.Invisible) != 0)
                {
                    flags &= ~GunkFlags.Invisible;

                    if ((flags & GunkFlags.Danger) != 0)
                        return danger;

                    if ((flags & GunkFlags.Stairs) != 0)
                        return stairs;

                    if ((flags & (GunkFlags.NoObjectCollision | GunkFlags.NoCharacterCollision)) == GunkFlags.NoObjectCollision)
                        return barrier;

                    if ((flags & (GunkFlags.NoObjectCollision | GunkFlags.NoCharacterCollision | GunkFlags.NoCollision)) == 0)
                        return impassable;

                    //
                    // What's the point of a invisible and no collision polygon?!
                    //

                    if ((flags & GunkFlags.NoCollision) != 0)
                        return null;

                    Console.Error.WriteLine("Unknown invisible material, fix tool: {0}", flags);
                }
                else
                {
                    if ((flags & (GunkFlags.TwoSided | GunkFlags.NoCollision)) == (GunkFlags.TwoSided | GunkFlags.NoCollision)
                        && polygon.Material != null
                        && polygon.Material.Name == "BLACKNESS")
                    {
                        return blackness;
                    }
                }

                return null;
            }

            private void EnsureMaterials()
            {
                if (ghost != null)
                    return;

                CreateGhost();
                CreateBarrier();
                CreateDanger();
                CreateDoor();
                CreateStairs();
                CreateImpassable();
                CreateBlackness();
                CreateFloor();
            }

            private void CreateBarrier()
            {
                var surface = new Surface(128, 128);

                var fill = new Color(0, 240, 20, 180);
                var sign = new Color(240, 20, 0, 255);

                surface.Fill(0, 0, 128, 128, fill);

                surface.Fill(0, 0, 128, 1, sign);
                surface.Fill(0, 127, 128, 1, sign);
                surface.Fill(0, 1, 1, 126, sign);
                surface.Fill(127, 1, 1, 126, sign);
                surface.Fill(64, 1, 1, 126, sign);
                surface.Fill(1, 64, 126, 1, sign);

                barrier = new Material("_marker_barrier", true)
                {
                    Flags = GunkFlags.NoObjectCollision | GunkFlags.Invisible,
                    Image = surface
                };
            }

            private void CreateImpassable()
            {
                var surface = new Surface(128, 128);

                var fill = new Color(240, 0, 20, 180);
                var sign = new Color(240, 20, 0, 255);

                surface.Fill(0, 0, 128, 128, fill);

                surface.Fill(0, 0, 128, 1, sign);
                surface.Fill(0, 127, 128, 1, sign);
                surface.Fill(0, 1, 1, 126, sign);
                surface.Fill(127, 1, 1, 126, sign);
                surface.Fill(64, 1, 1, 126, sign);
                surface.Fill(1, 64, 126, 1, sign);

                impassable = new Material("_marker_impassable", true)
                {
                    Flags = GunkFlags.Invisible,
                    Image = surface
                };
            }

            private void CreateGhost()
            {
                var surface = new Surface(128, 128);

                var border = new Color(16, 48, 240, 240);
                var fill = new Color(208, 240, 240, 80);

                surface.Fill(0, 0, 128, 128, fill);

                surface.Fill(0, 0, 128, 1, border);
                surface.Fill(0, 127, 128, 1, border);
                surface.Fill(0, 1, 1, 126, border);
                surface.Fill(127, 1, 1, 126, border);
                surface.Fill(64, 1, 1, 126, border);
                surface.Fill(1, 64, 126, 1, border);

                ghost = new Material("_marker_ghost", true);
                ghost.Flags = GunkFlags.Ghost | GunkFlags.TwoSided | GunkFlags.Transparent | GunkFlags.NoCollision;
                ghost.Image = surface;
            }

            private void CreateDoor()
            {
                var surface = new Surface(128, 128);

                var fill = new Color(240, 240, 0, 208);
                var line = new Color(0, 0, 240);

                surface.Fill(0, 0, 128, 128, fill);

                surface.Fill(1, 1, 126, 1, line);
                surface.Fill(1, 1, 1, 126, line);
                surface.Fill(1, 126, 126, 1, line);
                surface.Fill(126, 1, 1, 126, line);

                door = new Material("_marker_door", true)
                {
                    Flags = GunkFlags.DoorFrame | GunkFlags.TwoSided | GunkFlags.Transparent | GunkFlags.NoCollision,
                    Image = surface
                };
            }

            private void CreateDanger()
            {
                var surface = new Surface(128, 128);

                var fill = new Color(255, 10, 0, 208);
                var sign = new Color(255, 255, 255, 255);

                surface.Fill(0, 0, 128, 128, fill);
                surface.Fill(52, 16, 24, 64, sign);
                surface.Fill(52, 96, 24, 16, sign);

                danger = new Material("_marker_danger", true)
                {
                    Flags = GunkFlags.Danger | GunkFlags.Invisible | GunkFlags.NoCollision | GunkFlags.NoOcclusion,
                    Image = surface
                };
            }

            private void CreateStairs()
            {
                var surface = new Surface(128, 128);

                var fill = new Color(40, 240, 0, 180);
                var step = new Color(40, 0, 240, 180);

                surface.Fill(0, 0, 128, 128, fill);

                for (int y = 0; y < surface.Height; y += 32)
                    surface.Fill(0, y, surface.Width, 16, step);

                stairs = new Material("_marker_stairs", true)
                {
                    Flags = GunkFlags.Stairs | GunkFlags.Invisible | GunkFlags.NoObjectCollision | GunkFlags.TwoSided,
                    Image = surface
                };
            }

            private void CreateBlackness()
            {
                var surface = new Surface(16, 16, SurfaceFormat.BGRX);

                surface.Fill(0, 0, 16, 16, Color.Black);

                blackness = new Material("_marker_blackness", true)
                {
                    Flags = GunkFlags.TwoSided | GunkFlags.NoCollision,
                    Image = surface
                };
            }

            private void CreateFloor()
            {
                var surface = new Surface(256, 256);

                surface.Fill(0, 0, 16, 16, Color.White);

                for (int x = 0; x < 256; x += 4)
                {
                    surface.Fill(x, 0, 1, 256, Color.Black);
                    surface.Fill(0, x, 256, 1, Color.Black);
                }

                floor = new Material("_marker_floor", true)
                {
                    Flags = GunkFlags.NoCollision,
                    Image = surface
                };
            }

            public Material Barrier
            {
                get
                {
                    EnsureMaterials();
                    return barrier;
                }
            }

            public Material Ghost
            {
                get
                {
                    EnsureMaterials();
                    return ghost;
                }
            }

            public Material Danger
            {
                get
                {
                    EnsureMaterials();
                    return danger;
                }
            }

            public Material DoorFrame
            {
                get
                {
                    EnsureMaterials();
                    return door;
                }
            }

            public Material Stairs
            {
                get
                {
                    EnsureMaterials();
                    return stairs;
                }
            }

            public Material Floor
            {
                get
                {
                    EnsureMaterials();
                    return floor;
                }
            }

            public Material Blackness
            {
                get
                {
                    EnsureMaterials();
                    return blackness;
                }
            }
        }

        #endregion

        public MarkerMaterials Markers => markers;

        public Material NotFound
        {
            get
            {
                if (notFound == null)
                    notFound = GetMaterial("notfoundtex");

                return notFound;
            }
        }

        public IEnumerable<Material> All => materials.Values;

        public Material GetMaterial(string name)
        {
            Material material;

            if (!materials.TryGetValue(name, out material))
            {
                material = markers.GetMarker(name);

                if (material == null)
                    material = new Material(name);

                materials.Add(name, material);
            }

            if (name.StartsWith("lmap_", StringComparison.OrdinalIgnoreCase))
            {
                material.Flags |= GunkFlags.NoCollision | GunkFlags.NoOcclusion | GunkFlags.SoundTransparent;
            }

            return material;
        }
    }
}
