﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Reflection;

namespace Oni
{
#if !NETFX4
    internal delegate void Action();
    internal delegate void Action<T>(T arg1);
    internal delegate void Action<T1, T2>(T1 arg1, T2 args);
    internal delegate TResult Func<T1, TResult>(T1 arg1);
    internal delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
    internal delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
#endif

    internal static class Utils
    {
        private static string version;

        public static string Version
        {
            get
            {
                if (version == null)
                {
#if NETCORE
                    version = typeof(Utils).GetTypeInfo().Assembly.GetName().Version.ToString();
#else
                    version = typeof(Utils).Assembly.GetName().Version.ToString();
#endif
                }

                return version;
            }
        }

        public static string TagToString(int tag)
        {
            var chars = new char[4];

            chars[0] = (char)((tag >> 00) & 0xff);
            chars[1] = (char)((tag >> 08) & 0xff);
            chars[2] = (char)((tag >> 16) & 0xff);
            chars[3] = (char)((tag >> 24) & 0xff);

            return new string(chars);
        }

        public static int Align4(int value) => (value + 0x03) & ~0x03;
        public static int Align32(int value) => (value + 0x1f) & ~0x1f;

        public static short ByteSwap(short value)
        {
            return (short)((value >> 8) | (value << 8));
        }

        public static int ByteSwap(int value)
        {
            value = (value >> 16) | (value << 16);
            return ((value >> 8) & 0x00ff00ff) | ((value & 0x00ff00ff) << 8);
        }

        public static bool ArrayEquals<T>(T[] a1, T[] a2)
        {
            if (a1 == a2)
                return true;

            if (a1 == null || a2 == null)
                return false;

            if (a1.Length != a2.Length)
                return false;

            var comparer = EqualityComparer<T>.Default;

            for (int i = 0; i < a1.Length; i++)
            {
                if (!comparer.Equals(a1[i], a2[i]))
                    return false;
            }

            return true;
        }

        public static string CleanupTextureName(string name)
        {
            name = name.Replace('/', '_');

            if (name == "<none>")
                name = "none";

            return name;
        }

        private static readonly char[] wildcards = { '*', '?', '.' };

        private static void WildcardToRegex(string wexp, StringBuilder regexp)
        {
            for (int startIndex = 0; startIndex < wexp.Length;)
            {
                int i = wexp.IndexOfAny(wildcards, startIndex);

                if (i == -1)
                {
                    regexp.Append(wexp, startIndex, wexp.Length - startIndex);
                    break;
                }

                regexp.Append(wexp, startIndex, i - startIndex);

                if (wexp[i] == '.')
                    regexp.Append("\\.");
                if (wexp[i] == '*')
                    regexp.Append(".*");
                else if (wexp[i] == '?')
                    regexp.Append('.');

                startIndex = i + 1;
            }
        }

        public static Regex WildcardToRegex(string wexp)
        {
            if (string.IsNullOrEmpty(wexp))
                return null;

            var regexp = new StringBuilder();
            WildcardToRegex(wexp, regexp);
            return new Regex(regexp.ToString(), RegexOptions.Singleline);
        }

        public static Regex WildcardToRegex(List<string> wexps)
        {
            if (wexps.Count == 0)
                return null;

            var regex = new StringBuilder();

            foreach (var wexp in wexps)
            {
                if (regex.Length != 0)
                    regex.Append('|');

                regex.Append('(');
                WildcardToRegex(wexp, regex);
                regex.Append(')');
            }

            Console.WriteLine(regex.ToString());

            return new Regex(regex.ToString(), RegexOptions.Singleline);
        }

        private static byte[] buffer1;
        private static byte[] buffer2;

        public static bool AreFilesEqual(string filePath1, string filePath2)
        {
            if (buffer1 == null)
            {
                buffer1 = new byte[32768];
                buffer2 = new byte[32768];
            }

            using (var s1 = File.OpenRead(filePath1))
            using (var s2 = File.OpenRead(filePath2))
            {
                if (s1.Length != s2.Length)
                    return false;

                while (true)
                {
                    int r1 = s1.Read(buffer1, 0, buffer1.Length);
                    int r2 = s2.Read(buffer2, 0, buffer2.Length);

                    if (r1 != r2)
                        return false;

                    if (r1 == 0)
                        return true;

                    for (int i = 0; i < r1; i++)
                    {
                        if (buffer1[i] != buffer2[i])
                            return false;
                    }
                }
            }
        }

        
        public static bool IsFlagsEnum(Type enumType)
        {
#if NETCORE
            return (enumType.GetTypeInfo().GetCustomAttributes(typeof(FlagsAttribute), false).Any());
#else
            return (enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0);
#endif
        }

        public static void WriteEnum(Type enumType)
        {
            bool isFlags = IsFlagsEnum(enumType);
            Type underlyingType = Enum.GetUnderlyingType(enumType);

            if (isFlags)
                Console.WriteLine("flags {0}", enumType.Name);
            else
                Console.WriteLine("enum {0}", enumType.Name);

            foreach (string name in Enum.GetNames(enumType))
            {
                object value = Enum.Parse(enumType, name);

                if (isFlags)
                {
                    if (underlyingType == typeof(ulong))
                        Console.WriteLine("\t{0} = 0x{1:X16}", name, Convert.ToUInt64(value));
                    else
                        Console.WriteLine("\t{0} = 0x{1:X8}", name, Convert.ToUInt32(value));
                }
                else
                {
                    if (underlyingType == typeof(ulong))
                        Console.WriteLine("\t{0} = {1}", name, Convert.ToUInt64(value));
                    else
                        Console.WriteLine("\t{0} = {1}", name, Convert.ToInt32(value));
                }
            }

            Console.WriteLine();
        }

        public static IEnumerable<T> Reverse<T>(this IList<T> list)
        {
            for (int i = list.Count - 1; i >= 0; i--)
                yield return list[i];
        }

        public static T First<T>(this IList<T> list) => list[0];

        public static T Last<T>(this IList<T> list) => list[list.Count - 1];

        public static T LastOrDefault<T>(this List<T> list)
        {
            if (list.Count == 0)
                return default(T);

            return list[list.Count - 1];
        }

        public static float[] Negate(this float[] values)
        {
            float[] result = new float[values.Length];

            for (int i = 0; i < values.Length; i++)
                result[i] = -values[i];

            return result;
        }

        public static string CommonPrefix(List<string> strings)
        {
            string first = strings[0];

            for (int prefixLength = 0; prefixLength < first.Length; prefixLength++)
            {
                for (int i = 1; i < strings.Count; i++)
                {
                    string s = strings[i];

                    if (prefixLength >= s.Length || first[prefixLength] != s[prefixLength])
                        return first.Substring(0, prefixLength);
                }
            }

            return first;
        }

        public static void SkipSequence(this XmlReader xml, string name)
        {
            while (xml.IsStartElement(name))
                xml.Skip();
        }

        public static bool SkipEmpty(this XmlReader xml)
        {
            if (!xml.IsEmptyElement)
                return false;

            xml.Skip();
            return true;
        }

        //public static int CommonPrefixLength(string s1, string s2)
        //{
        //    int length = Math.Min(s1.Length, s2.Length);

        //    for (int i = 0; i < length; i++)
        //    {
        //        if (s1[i] != s2[i])
        //            return i;
        //    }

        //    return length;
        //}
    }

    /// <summary>
    /// A couple of IEnumerable extensions so we can run even on .NET 2.0
    /// </summary>
    internal static class Enumerable
    {
        public static bool Any<T>(this IEnumerable<T> source)
        {
            foreach (var t in source)
                return true;

            return false;
        }

        public static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var t in source)
            {
                if (predicate(t))
                    return true;
            }

            return false;
        }

        public static bool All<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var t in source)
            {
                if (!predicate(t))
                    return false;
            }

            return true;
        }

        public static IEnumerable<T> OfType<T>(this System.Collections.IEnumerable source)
            where T : class
        {
            foreach (var obj in source)
            {
                var t = obj as T;

                if (t != null)
                    yield return t;
            }
        }

        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source)
            where T : class
        {
            var set = new Dictionary<T, bool>();
            bool hasNull = false;

            foreach (var t in source)
            {
                if (t == null)
                {
                    if (!hasNull)
                    {
                        hasNull = true;
                        yield return null;
                    }
                }
                else if (!set.ContainsKey(t))
                {
                    set.Add(t, true);
                    yield return t;
                }
            }
        }

        public static int Count<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            var count = 0;

            foreach (var t in source)
                if (predicate(t))
                    count++;

            return count;
        }

        public static IEnumerable<T> Concatenate<T>(this IEnumerable<T> first, IEnumerable<T> second)
        {
            foreach (var t in first)
                yield return t;

            foreach (var t in second)
                yield return t;
        }

        public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var t in source)
            {
                if (predicate(t))
                    yield return t;
            }
        }

        public static bool IsEmpty<T>(this IEnumerable<T> source)
        {
            foreach (var t in source)
                return true;

            return false;
        }

        public static T First<T>(this IEnumerable<T> source)
        {
            foreach (var t in source)
                return t;

            throw new InvalidOperationException();
        }

        public static T First<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var t in source)
            {
                if (predicate(t))
                    return t;
            }

            throw new InvalidOperationException();
        }

        public static T FirstOrDefault<T>(this IEnumerable<T> source)
        {
            foreach (var t in source)
                return t;

            return default(T);
        }

        public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var t in source)
            {
                if (predicate(t))
                    return t;
            }

            return default(T);
        }

        public static IEnumerable<TOut> Select<TIn, TOut>(this IEnumerable<TIn> source, Func<TIn, TOut> selector)
        {
            foreach (var t in source)
                yield return selector(t);
        }

        public static IEnumerable<TOut> SelectMany<TIn, TOut>(this IEnumerable<TIn> source, Func<TIn, IEnumerable<TOut>> selector)
        {
            foreach (var tin in source)
            {
                foreach (var tout in selector(tin))
                    yield return tout;
            }
        }

        public static float Max(this IEnumerable<float> source)
        {
            var max = float.MinValue;

            foreach (var value in source)
                max = Math.Max(max, value);

            return max;
        }

        public static float Min<T>(this IEnumerable<T> source, Func<T, float> selector)
        {
            var min = float.MaxValue;

            foreach (var value in source)
                min = Math.Min(min, selector(value));

            return min;
        }

        public static int Min<T>(this IEnumerable<T> source, Func<T, int> selector)
        {
            var min = int.MaxValue;

            foreach (var value in source)
                min = Math.Min(min, selector(value));

            return min;
        }

        public static float Min(this IEnumerable<float> source)
        {
            var min = float.MaxValue;

            foreach (var value in source)
                min = Math.Min(min, value);

            return min;
        }

        public static float Max<T>(this IEnumerable<T> source, Func<T, float> selector)
        {
            var max = float.MinValue;

            foreach (var value in source)
                max = Math.Max(max, selector(value));

            return max;
        }

        public static int Max(this IEnumerable<int> source)
        {
            int max = int.MinValue;

            foreach (var value in source)
            {
                if (value > max)
                    max = value;
            }

            return max;
        }

        public static int Max<T>(this IEnumerable<T> source, Func<T, int> selector)
        {
            int max = int.MinValue;

            foreach (var item in source)
            {
                var value = selector(item);

                if (value > max)
                    max = value;
            }

            return max;
        }

        public static TOutput[] ConvertAll<TInput, TOutput>(this TInput[] input, Func<TInput, TOutput> converter)
        {
            var output = new TOutput[input.Length];

            for (int i = 0; i < output.Length; i++)
                output[i] = converter(input[i]);

            return output;
        }

        public static int Sum<T>(this IEnumerable<T> source, Func<T, int> selector)
        {
            int sum = 0;

            foreach (var value in source)
                sum += selector(value);

            return sum;
        }

        public static IEnumerable<T> Repeat<T>(T value, int count)
        {
            for (int i = 0; i < count; i++)
                yield return value;
        }

        public static T[] ToArray<T>(this IEnumerable<T> source)
        {
            var collection = source as ICollection<T>;

            if (collection != null)
            {
                var result = new T[collection.Count];
                collection.CopyTo(result, 0);
                return result;
            }

            return new List<T>(source).ToArray();
        }

        public static List<T> ToList<T>(this IEnumerable<T> source) => new List<T>(source);

        public static IEnumerable<T> Ring<T>(this IEnumerable<T> source)
        {
            foreach (T t in source)
            {
                yield return t;
            }

            foreach (T t in source)
            {
                yield return t;
                break;
            }
        }

        public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int count)
        {
            foreach (T t in source)
            {
                if (count <= 0)
                    yield return t;

                count--;
            }
        }
    }

    internal struct EmptyArray<T>
    {
        public static readonly T[] Value = new T[0];
    }

    internal struct ReadOnlyArray<T>
    {
        private readonly T[] array;

        public ReadOnlyArray(T[] array)
        {
            this.array = array;
        }

        public int Length => array.Length;
        public T this[int index] => array[index];
    }

    internal class TreeIterator<T> : IEnumerable<T>
    {
        private readonly IEnumerable<T> roots;
        private readonly Func<T, IEnumerable<T>> children;

        public TreeIterator(IEnumerable<T> roots, Func<T, IEnumerable<T>> children)
        {
            this.roots = roots;
            this.children = children;
        }

        public class Enumerator : IEnumerator<T>
        {
            public bool MoveNext()
            {
                throw new NotImplementedException();
            }

            public T Current
            {
                get { throw new NotImplementedException(); }
            }

            object IEnumerator.Current => Current;

            public void Dispose()
            {
            }

            public void Reset()
            {
                throw new NotSupportedException();
            }
        }

        public IEnumerator<T> GetEnumerator() => new Enumerator();
        IEnumerator IEnumerable.GetEnumerator() => new Enumerator();
    }
}

#if !NETFX4
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    internal sealed class ExtensionAttribute : Attribute
    {
    }
}
#endif
