using BoFilTest.Utils;
using IniParser.Model;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace BoFilTest.Domain
{
    public class Accessor<T>
    {
        private readonly Action<T> Setter;
        private readonly Func<T> Getter;

        public Accessor(Expression<Func<T>> expr)
        {
            var memberExpression = (MemberExpression)expr.Body;
            var instanceExpression = memberExpression.Expression;
            var parameter = Expression.Parameter(typeof(T));

            if (memberExpression.Member is PropertyInfo propertyInfo)
            {
                Setter = Expression.Lambda<Action<T>>(Expression.Call(instanceExpression, propertyInfo.GetSetMethod(), parameter), parameter).Compile();
                Getter = Expression.Lambda<Func<T>>(Expression.Call(instanceExpression, propertyInfo.GetGetMethod())).Compile();
            }
            else if (memberExpression.Member is FieldInfo fieldInfo)
            {
                Setter = Expression.Lambda<Action<T>>(Expression.Assign(memberExpression, parameter), parameter).Compile();
                Getter = Expression.Lambda<Func<T>>(Expression.Field(instanceExpression, fieldInfo)).Compile();
            }

        }

        public void Set(T value) => Setter(value);

        public T Get() => Getter();
    }

    public interface IPersistencyMapping
    {
        void Link(string name, Expression<Func<string>> variable);
        void Link(string name, Expression<Func<double>> variable);
        void Link(string name, Expression<Func<TimeSpan>> variable);
        void Link(string name, Expression<Func<int>> variable);
        void Link(string name, Expression<Func<bool>> variable);
    }

    public class FormWriter : IPersistencyMapping
    {
        private readonly KeyDataCollection store;

        public FormWriter(KeyDataCollection store)
        {
            this.store = store;
        }

        public void Link(string name, Expression<Func<string>> variable)
        {
            var accessor = new Accessor<string>(variable);
            store[name] = accessor.Get();
        }

        public void Link(string name, Expression<Func<double>> variable)
        {
            var accessor = new Accessor<double>(variable);
            store.WriteDouble(name, accessor.Get());
        }

        public void Link(string name, Expression<Func<TimeSpan>> variable)
        {
            var accessor = new Accessor<TimeSpan>(variable);
            store.WriteDouble(name, accessor.Get().TotalSeconds);
        }

        public void Link(string name, Expression<Func<int>> variable)
        {
            store.WriteInt(name, new Accessor<int>(variable).Get());
        }

        public void Link(string name, Expression<Func<bool>> variable)
        {
            store.WriteBool(name, new Accessor<bool>(variable).Get());
        }
    }

    public class FormLoader : IPersistencyMapping
    {
        private readonly DefaultsConfiguration config;

        public FormLoader(DefaultsConfiguration config)
        {
            this.config = config;
        }

        public void Link(string name, Expression<Func<string>> variable)
        {
            var accessor = new Accessor<string>(variable);
            accessor.Set(config[name]);
        }

        public void Link(string name, Expression<Func<double>> variable)
        {
            var accessor = new Accessor<double>(variable);
            accessor.Set(config.AsDouble(name));
        }

        public void Link(string name, Expression<Func<TimeSpan>> variable)
        {
            var accessor = new Accessor<TimeSpan>(variable);
            accessor.Set(TimeSpan.FromSeconds(config.AsDouble(name)));
        }

        public void Link(string name, Expression<Func<int>> variable)
        {
            new Accessor<int>(variable).Set(config.AsInt(name));
        }

        public void Link(string name, Expression<Func<bool>> variable)
        {
            new Accessor<bool>(variable).Set(config.AsBool(name));
        }
    }


    public class Form
    {
        public Form()
        {
            Date = DateTime.Now;
            ProjectNumber = "";
            PurchaseOrderNumber = "";
            Client = "";
            TestOperator = "";
            LabEquipment = "";
            AcquisitionInterval = TimeSpan.FromSeconds(0.2);

            WashingEnabled = false;
            WashingPassCount = 1;
            DemoisturizationRatio = 1.25;
            WashingPassFluidAmount = new List<double> { 100.0, 100.0, 100.0 };
            IntermediateDemoisturizationTime = TimeSpan.FromSeconds(10);
            PressTime = TimeSpan.FromSeconds(5);
            VaporizationTime = TimeSpan.FromSeconds(8);
            FilterArea = 1.0;
        }

        public void SetupNew()
        {
            Date = DateTime.Now;
        }

        public void ApplyMapping(IPersistencyMapping mapping)
        {
            mapping.Link("project.number", () => ProjectNumber);
            mapping.Link("purchase.order.number", () => PurchaseOrderNumber);
            mapping.Link("client", () => Client);
           
            mapping.Link("test.operator", () => TestOperator);
            mapping.Link("lab.equipment", () => LabEquipment);
            mapping.Link("acquisition.interval", () => AcquisitionInterval);
            mapping.Link("product", () => Product);
            mapping.Link("product.number", () => ProductNumber);

            mapping.Link("solid.density", () => SolidDensity);
            mapping.Link("liquid.density", () => LiquidDensity);
            mapping.Link("dyn.viscosity", () => DynamicViscosity);

            mapping.Link("suspension.concentration", () => SuspensionConcentration);
            mapping.Link("suspension.temperature", () => SuspensionTemperature);
            mapping.Link("flocculant.type", () => FlocculantType);
            mapping.Link("flocculant.concentration", () => FlocculantConcentration);
            mapping.Link("flocculant.amount", () => FlocculantAmount);
            mapping.Link("washing.medium", () => WashingMedium);
            mapping.Link("washing.fluid.temperature", () => WashingFluidTemperature);

            mapping.Link("washing.enabled", () => WashingEnabled);
            mapping.Link("washing.count", () => WashingPassCount);

            mapping.Link("washing.amount.1", () => WashingPassFluidAmount1);
            mapping.Link("washing.amount.2", () => WashingPassFluidAmount2);
            mapping.Link("washing.amount.3", () => WashingPassFluidAmount3);

            mapping.Link("intermediate.demoisturizing.time", () => IntermediateDemoisturizationTime);

            mapping.Link("gas.temperature", () => GasTemperature);
            mapping.Link("filter.cloth", () => FilterCloth);
            mapping.Link("filter.area", () => FilterArea);
            mapping.Link("filtration.pressure.difference", () => FilterPressureDifference);
            mapping.Link("demoisturization.ratio", () => DemoisturizationRatio);
            mapping.Link("press.enabled", () => PressEnabled);
            mapping.Link("press.pressure", () => PressPressure);
            mapping.Link("press.time", () => PressTime);

            mapping.Link("vaporization.enabled", () => VaporizationEnabled);
            mapping.Link("vaporization.time", () => VaporizationTime);

            mapping.Link("use.gas.breakthrough", () => UseGasBreakthrough);
        }

        public void LoadDefaults(DefaultsConfiguration config)
        {
            ApplyMapping(new FormLoader(config));
        }

        public DateTime Date { get; set; }
        public string ProjectNumber { get; set; }
        public string PurchaseOrderNumber { get; set; }
        public string Client { get; set; }
        public string TestOperator { get; set; }
        public string LabEquipment { get; set; }
        public TimeSpan AcquisitionInterval { get; set; }

        public string Product { get; set; }
        public string ProductNumber { get; set; }

        public double SolidDensity { get; set; }
        public double LiquidDensity { get; set; }
        public double DynamicViscosity { get; set; }

        public double SuspensionConcentration { get; set; }
        public double SuspensionTemperature { get; set; }

        public string FlocculantType { get; set; }
        public double FlocculantConcentration { get; set; }
        public double FlocculantAmount { get; set; }

        public string WashingMedium { get; set; }
        public double WashingFluidTemperature { get; set; }

        public double GasTemperature { get; set; }

        public string FilterCloth { get; set; }
        public double FilterArea { get; set; }
        public double FilterPressureDifference { get; set; }
        public double DemoisturizationRatio { get; set; }
        public bool WashingEnabled { get; set; }
        public int WashingPassCount { get; set; }
        /// <summary>
        /// Always has three passes
        /// </summary>
        public List<double> WashingPassFluidAmount { get; }

        public double WashingPassFluidAmount1
        {
            get { return WashingPassFluidAmount[0]; }
            set { WashingPassFluidAmount[0] = value; }
        }
        public double WashingPassFluidAmount2
        {
            get { return WashingPassFluidAmount[1]; }
            set { WashingPassFluidAmount[1] = value; }
        }
        public double WashingPassFluidAmount3
        {
            get { return WashingPassFluidAmount[2]; }
            set { WashingPassFluidAmount[2] = value; }
        }

        public TimeSpan IntermediateDemoisturizationTime { get; set; }
        public bool PressEnabled { get; set; }
        public double PressPressure { get; set; }
        public TimeSpan PressTime { get; set; }
        public bool VaporizationEnabled { get; set; }
        public TimeSpan VaporizationTime { get; set; }
        public bool UseGasBreakthrough { get; set; }
    }
}
