using System;
using System.Collections.Generic;
using System.IO;
using Unity.PerformanceTesting.Exceptions;
using UnityEngine;

namespace Unity.PerformanceTesting.Runtime
{
    internal static class Utils
    {
        public static string ResourcesPath => Path.Combine(Application.dataPath, "Resources");
        public const string TestRunPath = "Assets/Resources/" + TestRunInfo;
        public const string TestRunInfo = "PerformanceTestRunInfo.json";
        public const string PlayerPrefKeyRunJSON = "PT_Run";
        public const string RunSettingsPath = "Assets/Resources/" + RunSettings;
        public const string RunSettings = "PerformanceTestRunSettings.json";
        public const string PlayerPrefKeySettingsJSON = "PT_Settings";

        public static DateTime ConvertFromUnixTimestamp(long timestamp)
        {
            var offset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
            return offset.UtcDateTime;
        }

        public static long ConvertToUnixTimestamp(DateTime date)
        {
            var offset = new DateTimeOffset(date);
            return offset.ToUnixTimeMilliseconds();
        }

        public static double ConvertSample(SampleUnit from, SampleUnit to, double value)
        {
            if (from == to) return value;
            var ratio = GetRatio(from, to);
            return value * ratio;
        }

        public static double GetRatio(SampleUnit from, SampleUnit to)
        {
            double f = RelativeSampleUnit(from);
            double t = RelativeSampleUnit(to);
            return f / t;
        }

        public static double RelativeSampleUnit(SampleUnit unit)
        {
            switch (unit)
            {
                case SampleUnit.Nanosecond:
                    return 1;
                case SampleUnit.Microsecond:
                    return 1000;
                case SampleUnit.Millisecond:
                    return 1000000;
                case SampleUnit.Second:
                    return 1000000000;
                case SampleUnit.Byte:
                    return 1;
                case SampleUnit.Kilobyte:
                    return 1000;
                case SampleUnit.Megabyte:
                    return 1000000;
                case SampleUnit.Gigabyte:
                    return 1000000000;
                default:
                    throw new PerformanceTestException(
                        "Wrong SampleUnit type used.");
            }
        }

        public static void UpdateStatistics(this SampleGroup sampleGroup)
        {
            if (sampleGroup.Samples == null) return;
            var samples = sampleGroup.Samples;
            if (samples.Count < 2)
            {
                sampleGroup.Min = samples[0];
                sampleGroup.Max = samples[0];
                sampleGroup.Median = samples[0];
                sampleGroup.Average = samples[0];
                sampleGroup.Sum = samples[0];
                sampleGroup.StandardDeviation = 0;
            }
            else
            {
                sampleGroup.Min = Utils.Min(samples);
                sampleGroup.Max = Utils.Max(samples);
                sampleGroup.Median = Utils.GetMedianValue(samples);
                sampleGroup.Average = Utils.Average(samples);
                sampleGroup.Sum = Utils.Sum(samples);
                sampleGroup.StandardDeviation = Utils.GetStandardDeviation(samples, sampleGroup.Average);
            }
        }

        public static int GetZeroValueCount(List<double> samples)
        {
            var zeroValues = 0;
            foreach (var sample in samples)
            {
                if (Math.Abs(sample) < .0001f)
                {
                    zeroValues++;
                }
            }

            return zeroValues;
        }

        public static double GetMedianValue(List<double> samples)
        {
            var samplesClone = new List<double>(samples);
            var middleIndex = samplesClone.Count / 2;
            samplesClone.Sort();

            return samplesClone.Count % 2 != 0
                ? samplesClone[middleIndex]
                : (samplesClone[middleIndex - 1] + samplesClone[middleIndex]) / 2.0;
        }

        public static double GetPercentile(List<double> samples, double percentile)
        {
            if (percentile < 0.00001D)
                return percentile;

            var samplesClone = new List<double>(samples);
            samplesClone.Sort();

            if (samplesClone.Count == 1)
            {
                return samplesClone[0];
            }

            var rank = percentile * (samplesClone.Count + 1);
            var integral = (int)rank;
            var fractional = rank % 1;
            return samplesClone[integral - 1] + fractional * (samplesClone[integral] - samplesClone[integral - 1]);
        }

        public static double GetStandardDeviation(List<double> samples, double average)
        {
            double sumOfSquaresOfDifferences = 0.0D;
            foreach (var sample in samples)
            {
                sumOfSquaresOfDifferences += (sample - average) * (sample - average);
            }

            return Math.Sqrt(sumOfSquaresOfDifferences / samples.Count);
        }

        public static double Min(List<double> samples)
        {
            double min = Mathf.Infinity;
            foreach (var sample in samples)
            {
                if (sample < min) min = sample;
            }

            return min;
        }

        public static double Max(List<double> samples)
        {
            double max = Mathf.NegativeInfinity;
            foreach (var sample in samples)
            {
                if (sample > max) max = sample;
            }

            return max;
        }

        public static double Average(List<double> samples)
        {
            return Sum(samples) / samples.Count;
        }

        public static double Sum(List<double> samples)
        {
            double sum = 0.0D;
            foreach (var sample in samples)
            {
                sum += sample;
            }

            return sum;
        }

        public static string RemoveIllegalCharacters(string path)
        {
            string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

            foreach (char c in invalid)
            {
                path = path.Replace(c.ToString(), "");
            }

            return path;
        }

        public static string GetArg(string[] args, string name)
        {
            for (int i = 0; i < args.Length; i++)
            {
                if (args[i] == name && args.Length > i + 1)
                {
                    return args[i + 1];
                }
            }

            return null;
        }

        internal static int ToInt(this string s)
        {
            if (int.TryParse(s, out var i)) return i;
            return -1;
        }

        internal static SampleGroup[] CreateSampleGroupsFromMarkerNames(params string[] profilerMarkerNames)
        {
            if (profilerMarkerNames == null) return new List<SampleGroup>().ToArray();
            var sampleGroups = new List<SampleGroup>();
            foreach (var marker in profilerMarkerNames)
            {
                sampleGroups.Add(new SampleGroup(marker, SampleUnit.Nanosecond, false));
            }

            return sampleGroups.ToArray();
        }
    }
}