﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Timerial.Common.Mapping
{
    /// <summary>
    /// ITypeAdatper implementation. 
    /// <remarks>
    /// Really, this implementation only load RegisterTypesMapConfiguration 
    /// and create a global dictionary of mappers.
    /// </remarks>
    /// </summary>
    public sealed class TypeAdapter : ITypeAdapter
    {
        #region Properties

        private readonly Dictionary<string, ITypeMapConfiguration> _maps;
        private readonly IDynamicMapper _dynamicMapper;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="TypeAdapter"/> class
        /// </summary>
        /// <param name="maps">Array of maps</param>
        /// <param name="dynamicMapper">Dynamic mapper</param>
        public TypeAdapter(RegisterTypesMapBase[] maps, IDynamicMapper dynamicMapper)
        {
            _dynamicMapper = dynamicMapper;

            // create map adapters dictionary
            _maps = new Dictionary<string, ITypeMapConfiguration>();

            if (maps != null)
            {
                // foreach adapter's module in solution load mapping
                foreach (var module in maps)
                {
                    foreach (var map in module.Maps)
                    {
                        _maps.Add(map.Key, map.Value);
                    }
                }
            }
        }

        #endregion

        #region Private Method

        #endregion

        #region ITypeAdapter Implementation

        /// <summary>
        /// Adapt a source object to an instance of type <typeparamref name="TTarget"/>
        /// </summary>
        /// <typeparam name="TSource">Type of source item</typeparam>
        /// <typeparam name="TTarget">Type of target item</typeparam>
        /// <param name="source">Instance to adapt</param>
        /// <returns><paramref name="source"/> mapped to <typeparamref name="TTarget"/></returns>
        public TTarget Adapt<TSource, TTarget>(TSource source)
            where TSource : class
            where TTarget : class, new()
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            string descriptor = TypeMapConfigurationBase<TSource, TTarget>.GetDescriptor();

            if (_maps.ContainsKey(descriptor))
            {
                TypeMapConfigurationBase<TSource, TTarget> spec =
                    _maps[descriptor] as TypeMapConfigurationBase<TSource, TTarget>;

                return spec.Resolve(source);
            }

            // do dynamic mapping if a static one was not registered
            return _dynamicMapper.Map<TSource, TTarget>(source);
        }

        /// <summary>
        /// Adapt a source object to an instance of type <typeparamref name="TTarget"/> using nested object for map completion
        /// </summary>
        /// <typeparam name="TSource">Type of source item</typeparam>
        /// <typeparam name="TTarget">Type of target item</typeparam>
        /// <param name="source">Instance of source to map</param>
        /// <param name="moreSources">More sources for mapping</param>
        /// <returns><paramref name="source"/> mapped to <typeparamref name="TTarget"/></returns>
        public TTarget Adapt<TSource, TTarget>(TSource source, params object[] moreSources)
            where TSource : class
            where TTarget : class, new()
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            string descriptor = TypeMapConfigurationBase<TSource, TTarget>.GetDescriptor();

            if (_maps.ContainsKey(descriptor))
            {
                var spec = _maps[descriptor] as TypeMapConfigurationBase<TSource, TTarget>;

                return spec.Resolve(source, moreSources);
            }

            throw new InvalidOperationException(string.Format("NotMapFoundForTypeAdapter {0} {1}", typeof(TSource).FullName, typeof(TTarget).FullName));
        }

        #endregion
    }
}
