namespace SRDebugger.Services.Implementation
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using SRF;
    using SRF.Service;
    using UnityEngine;

    /// <summary>
    /// Reports system specifications and environment information. Information that can
    /// be used to identify a user is marked as private, and won't be included in any generated
    /// reports.
    /// </summary>
    [Service(typeof(ISystemInformationService))]
    public class StandardSystemInformationService : ISystemInformationService
    {
        private readonly Dictionary<string, IList<InfoEntry>> _info = new Dictionary<string, IList<InfoEntry>>();

        public StandardSystemInformationService()
        {
            CreateDefaultSet();
        }

        public IEnumerable<string> GetCategories()
        {
            return _info.Keys;
        }

        public IList<InfoEntry> GetInfo(string category)
        {
            IList<InfoEntry> list;

            if (!_info.TryGetValue(category, out list))
            {
                Debug.LogError("[SystemInformationService] Category not found: {0}".Fmt(category));
                return new InfoEntry[0];
            }

            return list;
        }

        public void Add(InfoEntry info, string category = "Default")
        {
            IList<InfoEntry> list;

            if (!_info.TryGetValue(category, out list))
            {
                list = new List<InfoEntry>();
                _info.Add(category, list);
            }

            if (list.Any(p => p.Title == info.Title))
            {
                throw new ArgumentException("An InfoEntry object with the same title already exists in that category.", "info");
            }

            list.Add(info);
        }

        public Dictionary<string, Dictionary<string, object>> CreateReport(bool includePrivate = false)
        {
            var dict = new Dictionary<string, Dictionary<string, object>>();

            foreach (var keyValuePair in _info)
            {
                var category = new Dictionary<string, object>();

                foreach (var systemInfo in keyValuePair.Value)
                {
                    if (systemInfo.IsPrivate && !includePrivate)
                    {
                        continue;
                    }

                    category.Add(systemInfo.Title, systemInfo.Value);
                }

                dict.Add(keyValuePair.Key, category);
            }

            return dict;
        }

        private void CreateDefaultSet()
        {
            _info.Add("System", new[]
            {
                InfoEntry.Create("Operating System", UnityEngine.SystemInfo.operatingSystem),
                InfoEntry.Create("Device Name", UnityEngine.SystemInfo.deviceName, true),
                InfoEntry.Create("Device Type", UnityEngine.SystemInfo.deviceType),
                InfoEntry.Create("Device Model", UnityEngine.SystemInfo.deviceModel),
                InfoEntry.Create("CPU Type", UnityEngine.SystemInfo.processorType),
                InfoEntry.Create("CPU Count", UnityEngine.SystemInfo.processorCount),
                InfoEntry.Create("System Memory", SRFileUtil.GetBytesReadable(((long) UnityEngine.SystemInfo.systemMemorySize)*1024*1024))
                //Info.Create("Process Name", () => Process.GetCurrentProcess().ProcessName)
            });

            if (SystemInfo.batteryStatus != BatteryStatus.Unknown)
            {
                _info.Add("Battery", new[]
                {
                    InfoEntry.Create("Status", UnityEngine.SystemInfo.batteryStatus),
                    InfoEntry.Create("Battery Level", UnityEngine.SystemInfo.batteryLevel)
                });
            }

#if ENABLE_IL2CPP
            const string IL2CPP = "Yes";
#else
            const string IL2CPP = "No";
#endif

            _info.Add("Unity", new[]
            {
                InfoEntry.Create("Version", Application.unityVersion),
                InfoEntry.Create("Debug", Debug.isDebugBuild),
                InfoEntry.Create("Unity Pro", Application.HasProLicense()),
                InfoEntry.Create("Genuine",
                    "{0} ({1})".Fmt(Application.genuine ? "Yes" : "No",
                        Application.genuineCheckAvailable ? "Trusted" : "Untrusted")),
                InfoEntry.Create("System Language", Application.systemLanguage),
                InfoEntry.Create("Platform", Application.platform),
                InfoEntry.Create("Install Mode", Application.installMode),
                InfoEntry.Create("Sandbox", Application.sandboxType),
                InfoEntry.Create("IL2CPP", IL2CPP),
                InfoEntry.Create("Application Version", Application.version),
                InfoEntry.Create("SRDebugger Version", SRDebug.Version),
            });

            _info.Add("Display", new[]
            {
                InfoEntry.Create("Resolution", () => Screen.width + "x" + Screen.height),
                InfoEntry.Create("DPI", () => Screen.dpi),
                InfoEntry.Create("Fullscreen", () => Screen.fullScreen),
                InfoEntry.Create("Fullscreen Mode", () => Screen.fullScreenMode),
                InfoEntry.Create("Orientation", () => Screen.orientation),
            }); 

            _info.Add("Runtime", new[]
            {
                InfoEntry.Create("Play Time", () => Time.unscaledTime),
                InfoEntry.Create("Level Play Time", () => Time.timeSinceLevelLoad),
#if UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2
                InfoEntry.Create("Current Level", () => Application.loadedLevelName),
#else
                InfoEntry.Create("Current Level", () =>
                {
                    var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
                    return "{0} (Index: {1})".Fmt(activeScene.name, activeScene.buildIndex);
                }),
#endif
                InfoEntry.Create("Quality Level",
                    () =>
                        QualitySettings.names[QualitySettings.GetQualityLevel()] + " (" +
                        QualitySettings.GetQualityLevel() + ")")
            });

            // Check for cloud build manifest
            var cloudBuildManifest = (TextAsset)Resources.Load("UnityCloudBuildManifest.json");
            var manifestDict = cloudBuildManifest != null
                ? Json.Deserialize(cloudBuildManifest.text) as Dictionary<string, object>
                : null;

            if (manifestDict != null)
            {
                var manifestList = new List<InfoEntry>(manifestDict.Count);

                foreach (var kvp in manifestDict)
                {
                    if (kvp.Value == null)
                    {
                        continue;
                    }

                    var value = kvp.Value.ToString();
                    manifestList.Add(InfoEntry.Create(GetCloudManifestPrettyName(kvp.Key), value));
                }

                _info.Add("Build", manifestList);
            }

            _info.Add("Features", new[]
            {
                InfoEntry.Create("Location", UnityEngine.SystemInfo.supportsLocationService),
                InfoEntry.Create("Accelerometer", UnityEngine.SystemInfo.supportsAccelerometer),
                InfoEntry.Create("Gyroscope", UnityEngine.SystemInfo.supportsGyroscope),
                InfoEntry.Create("Vibration", UnityEngine.SystemInfo.supportsVibration),
                InfoEntry.Create("Audio", UnityEngine.SystemInfo.supportsAudio)
            });

#if UNITY_IOS

            _info.Add("iOS", new[] {

#if UNITY_5 || UNITY_5_3_OR_NEWER
                InfoEntry.Create("Generation", UnityEngine.iOS.Device.generation),
                InfoEntry.Create("Ad Tracking", UnityEngine.iOS.Device.advertisingTrackingEnabled),
#else
                InfoEntry.Create("Generation", iPhone.generation),
                InfoEntry.Create("Ad Tracking", iPhone.advertisingTrackingEnabled),
#endif
            });

#endif
#pragma warning disable 618
            _info.Add("Graphics - Device", new[]
            {
                InfoEntry.Create("Device Name", UnityEngine.SystemInfo.graphicsDeviceName),
                InfoEntry.Create("Device Vendor", UnityEngine.SystemInfo.graphicsDeviceVendor),
                InfoEntry.Create("Device Version", UnityEngine.SystemInfo.graphicsDeviceVersion),
                InfoEntry.Create("Graphics Memory", SRFileUtil.GetBytesReadable(((long) UnityEngine.SystemInfo.graphicsMemorySize)*1024*1024)),
                InfoEntry.Create("Max Tex Size", UnityEngine.SystemInfo.maxTextureSize),
            });

            _info.Add("Graphics - Features", new[]
            {
                InfoEntry.Create("UV Starts at top", UnityEngine.SystemInfo.graphicsUVStartsAtTop),
                InfoEntry.Create("Shader Level", UnityEngine.SystemInfo.graphicsShaderLevel),
                InfoEntry.Create("Multi Threaded", UnityEngine.SystemInfo.graphicsMultiThreaded),
                InfoEntry.Create("Hidden Service Removal (GPU)", UnityEngine.SystemInfo.hasHiddenSurfaceRemovalOnGPU),
                InfoEntry.Create("Uniform Array Indexing (Fragment Shaders)", UnityEngine.SystemInfo.hasDynamicUniformArrayIndexingInFragmentShaders),
                InfoEntry.Create("Shadows", UnityEngine.SystemInfo.supportsShadows),
                InfoEntry.Create("Raw Depth Sampling (Shadows)", UnityEngine.SystemInfo.supportsRawShadowDepthSampling),
                InfoEntry.Create("Motion Vectors", UnityEngine.SystemInfo.supportsMotionVectors),
                InfoEntry.Create("Cubemaps", UnityEngine.SystemInfo.supportsRenderToCubemap),
                InfoEntry.Create("Image Effects", UnityEngine.SystemInfo.supportsImageEffects),
                InfoEntry.Create("3D Textures", UnityEngine.SystemInfo.supports3DTextures),
                InfoEntry.Create("2D Array Textures", UnityEngine.SystemInfo.supports2DArrayTextures),
                InfoEntry.Create("3D Render Textures", UnityEngine.SystemInfo.supports3DRenderTextures),
                InfoEntry.Create("Cubemap Array Textures", UnityEngine.SystemInfo.supportsCubemapArrayTextures),
                InfoEntry.Create("Copy Texture Support", UnityEngine.SystemInfo.copyTextureSupport),
                InfoEntry.Create("Compute Shaders", UnityEngine.SystemInfo.supportsComputeShaders),
                InfoEntry.Create("Instancing", UnityEngine.SystemInfo.supportsInstancing),
                InfoEntry.Create("Hardware Quad Topology", UnityEngine.SystemInfo.supportsHardwareQuadTopology),
                InfoEntry.Create("32-bit index buffer", UnityEngine.SystemInfo.supports32bitsIndexBuffer),
                InfoEntry.Create("Sparse Textures", UnityEngine.SystemInfo.supportsSparseTextures),
                InfoEntry.Create("Render Target Count", UnityEngine.SystemInfo.supportedRenderTargetCount),
                InfoEntry.Create("Separated Render Targets Blend", UnityEngine.SystemInfo.supportsSeparatedRenderTargetsBlend),
                InfoEntry.Create("Multisampled Textures", UnityEngine.SystemInfo.supportsMultisampledTextures),
                InfoEntry.Create("Texture Wrap Mirror Once", UnityEngine.SystemInfo.supportsTextureWrapMirrorOnce),
                InfoEntry.Create("Reversed Z Buffer", UnityEngine.SystemInfo.usesReversedZBuffer)
            });
#pragma warning restore 618

        }

        private static string GetCloudManifestPrettyName(string name)
        {
            switch (name)
            {
                case "scmCommitId":
                    return "Commit";

                case "scmBranch":
                    return "Branch";

                case "cloudBuildTargetName":
                    return "Build Target";

                case "buildStartTime":
                    return "Build Date";
            }

            // Return name with first letter capitalised
            return name.Substring(0, 1).ToUpper() + name.Substring(1);
        }
    }
}