using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using ModestTree;
using Zenject.Internal;

#if !NOT_UNITY3D

#if UNITY_5_5
using UnityEngine.Profiling;
#endif
#endif

namespace Zenject
{
    public class ProfileBlock : IDisposable
    {
#if UNITY_EDITOR && ZEN_PROFILING_ENABLED
        static int _blockCount;
        static ProfileBlock _instance = new ProfileBlock();
        static Dictionary<int, string> _nameCache = new Dictionary<int, string>();

        ProfileBlock()
        {
        }

        public static Regex ProfilePattern
        {
            get;
            set;
        }

        static int GetHashCode(object p1, object p2)
        {
            unchecked // Overflow is fine, just wrap
            {
                int hash = 17;
                hash = hash * 29 + p1.GetHashCode();
                hash = hash * 29 + p2.GetHashCode();
                return hash;
            }
        }

        static int GetHashCode(object p1, object p2, object p3)
        {
            unchecked // Overflow is fine, just wrap
            {
                int hash = 17;
                hash = hash * 29 + p1.GetHashCode();
                hash = hash * 29 + p2.GetHashCode();
                hash = hash * 29 + p3.GetHashCode();
                return hash;
            }
        }

        public static ProfileBlock Start(string sampleNameFormat, object obj1, object obj2)
        {
            // We need to ensure that we do not have per-frame allocations in ProfileBlock
            // to avoid infecting the test too much, so use a cache of formatted strings given
            // the input values
            // This only works if the input values do not change per frame
            var hash = GetHashCode(sampleNameFormat, obj1, obj2);

            string formatString;

            if (!_nameCache.TryGetValue(hash, out formatString))
            {
                formatString = string.Format(sampleNameFormat, obj1, obj2);
                _nameCache.Add(hash, formatString);
            }

            return StartInternal(formatString);
        }

        public static ProfileBlock Start(string sampleNameFormat, object obj)
        {
            // We need to ensure that we do not have per-frame allocations in ProfileBlock
            // to avoid infecting the test too much, so use a cache of formatted strings given
            // the input values
            // This only works if the input values do not change per frame
            var hash = GetHashCode(sampleNameFormat, obj);

            string formatString;

            if (!_nameCache.TryGetValue(hash, out formatString))
            {
                formatString = string.Format(sampleNameFormat, obj);
                _nameCache.Add(hash, formatString);
            }

            return StartInternal(formatString);
        }

        public static ProfileBlock Start(string sampleName)
        {
            return StartInternal(sampleName);
        }

        static ProfileBlock StartInternal(string sampleName)
        {
            if (!UnityEngine.Profiling.Profiler.enabled)
            {
                return null;
            }

            if (ProfilePattern == null || ProfilePattern.Match(sampleName).Success)
            {
                UnityEngine.Profiling.Profiler.BeginSample(sampleName);
                _blockCount++;
                return _instance;
            }

            return null;
        }

        public void Dispose()
        {
            _blockCount--;
            UnityEngine.Profiling.Profiler.EndSample();
            Assert.That(_blockCount >= 0);
        }

#else
        ProfileBlock(string sampleName, bool rootBlock)
        {
        }

        ProfileBlock(string sampleName)
            : this(sampleName, false)
        {
        }

        public static Regex ProfilePattern
        {
            get;
            set;
        }

        public static ProfileBlock Start()
        {
            return null;
        }

        public static ProfileBlock Start(string sampleNameFormat, object obj1, object obj2)
        {
            return null;
        }

        // Remove the call completely for builds
        public static ProfileBlock Start(string sampleNameFormat, object obj)
        {
            return null;
        }

        // Remove the call completely for builds
        public static ProfileBlock Start(string sampleName)
        {
            return null;
        }

        public void Dispose()
        {
        }
#endif
    }
}
