using System;
using System.Collections.Generic;
using LoreSoft.MathExpressions.Properties;
using LoreSoft.MathExpressions.UnitConversion;
using System.Globalization;
using LoreSoft.MathExpressions.Metadata;
using System.Reflection;
namespace LoreSoft.MathExpressions
{
    /// 
    /// A class representing unit convertion expressions.
    /// 
    public class ConvertExpression : ExpressionBase
    {
        private static Dictionary convertionCache;
        private static object cacheLock = new object();
        private ConvertionMap current;
        private string expression;
        /// The format of a convertion expression.
        public const string ExpressionFormat = "[{0}->{1}]";
        /// Initializes a new instance of the  class.
        /// The convertion expression for this instance.
        public ConvertExpression(string expression)
        {
            VerifyCache();
            if (!convertionCache.ContainsKey(expression))
                throw new ArgumentException(Resources.InvalidConvertionExpression + expression, "expression");
            this.expression = expression;
            current = convertionCache[expression];
            base.Evaluate = new MathEvaluate(Convert);
        }
        /// Gets the number of arguments this expression uses.
        /// The argument count.
        public override int ArgumentCount
        {
            get { return 1; }
        }
        /// Convert the numbers to the new unit.
        /// The numbers used in the convertion.
        /// The result of the convertion execution.
        /// When numbers is null.
        /// When the length of numbers do not equal .
        public double Convert(double[] numbers)
        {
            base.Validate(numbers);
            double fromValue = numbers[0];
            switch (current.UnitType)
            {
                case UnitType.Length:
                    return LengthConverter.Convert(
                        (LengthUnit) current.FromUnit,
                        (LengthUnit) current.ToUnit,
                        fromValue);
                case UnitType.Mass:
                    return MassConverter.Convert(
                        (MassUnit) current.FromUnit,
                        (MassUnit) current.ToUnit,
                        fromValue);
                case UnitType.Speed:
                    return SpeedConverter.Convert(
                        (SpeedUnit) current.FromUnit,
                        (SpeedUnit) current.ToUnit,
                        fromValue);
                case UnitType.Temperature:
                    return TemperatureConverter.Convert(
                        (TemperatureUnit) current.FromUnit,
                        (TemperatureUnit) current.ToUnit,
                        fromValue);
                case UnitType.Time:
                    return TimeConverter.Convert(
                        (TimeUnit) current.FromUnit,
                        (TimeUnit) current.ToUnit,
                        fromValue);
                case UnitType.Volume:
                    return VolumeConverter.Convert(
                        (VolumeUnit) current.FromUnit,
                        (VolumeUnit) current.ToUnit,
                        fromValue);
                default:
                    throw new ArgumentOutOfRangeException("numbers");
            }
        }
        ///
        /// Determines whether the specified expression name is for unit convertion.
        ///
        ///The expression to check.
        ///true if the specified expression is a unit convertion; otherwise, false.
        public static bool IsConvertExpression(string expression)
        {
            //do basic checks before creating cache
            if (string.IsNullOrEmpty(expression))
                return false;
            if (expression[0] != '[')
                return false;
            VerifyCache();
            return convertionCache.ContainsKey(expression);
        }
        private static void VerifyCache()
        {
            if (convertionCache != null)
                return;
            lock (cacheLock)
            {
                if (convertionCache != null)
                    return;
                convertionCache = new Dictionary(
                    StringComparer.OrdinalIgnoreCase);
                AddToCache(UnitType.Length);
                AddToCache(UnitType.Mass);
                AddToCache(UnitType.Speed);
                AddToCache(UnitType.Temperature);
                AddToCache(UnitType.Time);
                AddToCache(UnitType.Volume);
            }
        }
        private static void AddToCache(UnitType unitType) 
            where T : struct, IComparable, IFormattable, IConvertible
        {
            Type enumType = typeof(T);
            int[] a = (int[])Enum.GetValues(enumType);
            for (int x = 0; x < a.Length; x++)
            {
                MemberInfo parentInfo = GetMemberInfo(enumType, Enum.GetName(enumType, x));
                string parrentKey = AttributeReader.GetAbbreviation(parentInfo);
                
                for (int i = 0; i < a.Length; i++)
                {
                    if (x == i)
                        continue;
                    MemberInfo info = GetMemberInfo(enumType, Enum.GetName(enumType, i));
                    string key = string.Format(
                        CultureInfo.InvariantCulture,
                        ExpressionFormat,
                        parrentKey,
                        AttributeReader.GetAbbreviation(info));
                    convertionCache.Add(
                        key, new ConvertionMap(unitType, x, i));
                }
            }
        }
        private static MemberInfo GetMemberInfo(Type type, string name)
        {
            MemberInfo[] info = type.GetMember(name);
            if (info == null || info.Length == 0)
                return null;
            return info[0];
        }
        /// 
        /// Returns a  that represents the current .
        /// 
        /// 
        /// A  that represents the current .
        /// 
        /// 2
        public override string ToString()
        {
            return expression;
        }
        private class ConvertionMap
        {
            public ConvertionMap(UnitType unitType, int fromUnit, int toUnit)
            {
                _unitType = unitType;
                _fromUnit = fromUnit;
                _toUnit = toUnit;
            }
            private UnitType _unitType;
            public UnitType UnitType
            {
                get { return _unitType; }
            }
            private int _fromUnit;
            public int FromUnit
            {
                get { return _fromUnit; }
            }
            private int _toUnit;
            public int ToUnit
            {
                get { return _toUnit; }
            }
            public override string ToString()
            {
                return string.Format(
                    CultureInfo.CurrentCulture, 
                    "{0}, [{1}->{2}]", 
                    _unitType, 
                    _fromUnit, 
                    _toUnit);
            }
        }
    }
}