using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Content.Client.Interfaces.Parallax;
using Nett;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.Log;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

namespace Content.Client.Parallax
{
    internal sealed class ParallaxManager : IParallaxManager, IPostInjectInit
    {
#pragma warning disable 649
        [Dependency] private readonly IResourceCache _resourceCache;
        [Dependency] private readonly ILogManager _logManager;
        [Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649

        private static readonly ResourcePath ParallaxConfigPath = new ResourcePath("/parallax_config.toml");

        // Both of these below are in the user directory.
        private static readonly ResourcePath ParallaxPath = new ResourcePath("/parallax_cache.png");
        private static readonly ResourcePath ParallaxConfigOld = new ResourcePath("/parallax_config_old");

        public event Action<Texture> OnTextureLoaded;
        public Texture ParallaxTexture { get; private set; }

        public async void LoadParallax()
        {
            if (!_configurationManager.GetCVar<bool>("parallax.enabled"))
            {
                return;
            }

            var debugParallax = _configurationManager.GetCVar<bool>("parallax.debug");
            string contents;
            TomlTable table;
            // Load normal config into memory
            if (!_resourceCache.TryContentFileRead(ParallaxConfigPath, out var configStream))
            {
                Logger.ErrorS("parallax", "Parallax config not found.");
                return;
            }

            using (configStream)
            {
                using (var reader = new StreamReader(configStream, EncodingHelpers.UTF8))
                {
                    contents = reader.ReadToEnd();
                }

                if (!debugParallax && _resourceCache.UserData.Exists(ParallaxConfigOld))
                {
                    bool match;
                    using (var data = _resourceCache.UserData.Open(ParallaxConfigOld, FileMode.Open))
                    using (var reader = new StreamReader(data, EncodingHelpers.UTF8))
                    {
                        match = reader.ReadToEnd() == contents;
                    }

                    if (match)
                    {
                        using (var stream = _resourceCache.UserData.Open(ParallaxPath, FileMode.Open))
                        {
                            ParallaxTexture = Texture.LoadFromPNGStream(stream, "Parallax");
                        }

                        OnTextureLoaded?.Invoke(ParallaxTexture);
                        return;
                    }
                }

                table = Toml.ReadString(contents);
            }

            List<Image<Rgba32>> debugImages = null;
            if (debugParallax)
            {
                debugImages = new List<Image<Rgba32>>();
            }

            var sawmill = _logManager.GetSawmill("parallax");
            // Generate the parallax in the thread pool.
            var image = await Task.Run(() =>
                ParallaxGenerator.GenerateParallax(table, new Size(1920, 1080), sawmill, debugImages));
            // And load it in the main thread for safety reasons.
            ParallaxTexture = Texture.LoadFromImage(image, "Parallax");

            // Store it and CRC so further game starts don't need to regenerate it.
            using (var stream = _resourceCache.UserData.Open(ParallaxPath, FileMode.Create))
            {
                image.SaveAsPng(stream);
            }

            if (debugParallax)
            {
                var i = 0;
                foreach (var debugImage in debugImages)
                {
                    using (var stream = _resourceCache.UserData.Open(new ResourcePath($"/parallax_debug_{i}.png"),
                        FileMode.Create))
                    {
                        debugImage.SaveAsPng(stream);
                    }

                    i += 1;
                }
            }

            image.Dispose();

            using (var stream = _resourceCache.UserData.Open(ParallaxConfigOld, FileMode.Create))
            using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
            {
                writer.Write(contents);
            }

            OnTextureLoaded?.Invoke(ParallaxTexture);
        }

        public void PostInject()
        {
            _configurationManager.RegisterCVar("parallax.enabled", true);
            _configurationManager.RegisterCVar("parallax.debug", false);
        }
    }
}
