using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Raytheon.Units
{
   /// <summary>
   /// Contains info related to a unit: to parse, print, and convert values
   /// </summary>
   internal class UnitInfo<T>
   {
      public UnitInfo( List<string> abbreviation,
                      Func<double, T> conversionMethod,
                      MethodInfo propertyGetter )
      {
         Abbreviation = abbreviation;
         ConversionMethod = conversionMethod;
         PropertyGetter = propertyGetter;
      }

      /// <summary>
      /// standard text abbreviation for this unit
      /// </summary>
      public List<string> Abbreviation { get; private set; }

      /// <summary>
      /// method for converting string to this unit
      /// </summary>
      public Func<double, T> ConversionMethod { get; private set; }

      /// <summary>
      /// get method for the property associated with this unit
      /// </summary>
      public MethodInfo PropertyGetter { get; set; }
   }

   internal class Common
   {
      /// <summary>
      /// Converts a string that represents a number with a unit to the actual 
      /// unit type.
      /// </summary>
      /// <typeparam name="E">Enumeration of the Unit abbreviation</typeparam>
      /// <typeparam name="T">The actual unit type, i.e Current</typeparam>
      /// <param name="s">String that represents a unit value, i.e 1.23 Amps</param>
      /// <param name="unitInfo">Look up table for how to convert the units</param>
      /// <returns>
      /// The converted number to the correct unit type. If unable to parse, will return the first
      /// <see cref="UnitInfo{T}.ConversionMethod"/> with a value of <see cref="double.NaN"/>
      /// </returns>
      public static T Parse<E, T>( string s, IDictionary<E, UnitInfo<T>> unitInfo )
      {
         const string FloatingPointPattern = @"([-+]?[0-9]+(\.[0-9]+)?([Ee][+-][0-9]+)?)";
         const string AbbreviationPattern = @"([\w/\^]*)";
         string pattern = FloatingPointPattern + @"\s*" + AbbreviationPattern;

         Regex regEx = new Regex( pattern );
         Match match = regEx.Match( s );

         if(!match.Success || match.Groups.Count == 0 )
         {
            throw new FormatException( $"UUnknown value : {s}" );
         }

         string abbreviation = match.Groups[match.Groups.Count - 1].Value;

         IEnumerable<UnitInfo<T>> x = unitInfo.Where( kvp => kvp.Value.Abbreviation.Contains( abbreviation, StringComparer.CurrentCultureIgnoreCase ) )
             .Select( kvp => kvp.Value );

         // abbreviation not found?
         if ( x.Count( ) == 0 )
         {
            throw new FormatException( $"Unknown units: '{abbreviation}'" );
         }

         double value = double.Parse( match.Groups[1].Value );

         return x.First( ).ConversionMethod( value );
      }
   }
}
