using BoFilTest.Domain.Modules;
using BoFilTest.Domain.Modules.Balance;
using BoFilTest.Utils;
using Optional;
using System;
using Unity;
using Unity.Lifetime;

namespace BoFilTest.Domain
{
    public interface IHardwareControl
    {
        MeasuredValues ReadAll();
        IGasFlowMeasure GasFlowMeasure { get; }
        void Cleanup();
    }

    public class HardwareControlWithFailSafe : IHardwareControl
    {
        private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

        private IBalance balance;
        private IGasFlowMeasure gasFlowMeasure;
        private IPressureMeasure pressureMeasure;
        private IExtraMeasure extraMeasure;
        private readonly IUnityContainer scopedContainer;

        public HardwareControlWithFailSafe(IUnityContainer container, Configuration config)
        {
            scopedContainer = container.CreateChildContainer();

            // Add a factory to connect to the voegtlin device via serial port, if necessary
            this.scopedContainer.RegisterFactory<VoegtlinDeviceModbusRtuConnection>(
                c => new VoegtlinDeviceModbusRtuConnection(config.Voegtlin().ModbusPort()),
                new ContainerControlledLifetimeManager());

            // Add a factory that uses the connection for an actual device
            this.scopedContainer.RegisterFactory<VoegtlinDevice>(
                c => new VoegtlinDevice(c.Resolve<VoegtlinDeviceModbusRtuConnection>().Master, config.Voegtlin().SlaveAddress()),
                new ContainerControlledLifetimeManager());

            var factory = new HardwareFactory(scopedContainer, config);
            this.balance = factory.CreateBalance();
            this.gasFlowMeasure = factory.CreateGasFlowMeasure();
            this.pressureMeasure = factory.CreatePressureMeasure();
            this.extraMeasure = factory.CreateExtraMeasure();
        }

        public MeasuredValues ReadAll()
        {
            return new MeasuredValues(
                weight: ReadBalanceOrDisable(),
                flow: ReadGasFlowOrDisable(),
                pressure: ReadPressureOrDisable(),
                extraValue: ReadExtraOrDisable());
        }
        
        public IGasFlowMeasure GasFlowMeasure => gasFlowMeasure;

        private Option<ExtraValue> ReadExtraOrDisable()
        {
            try
            {
                return this.extraMeasure.ReadValue();
            }
            catch (Exception e)
            {
                logger.Error("While reading extra measure: {0}. Disabling!", e.Message);
                this.extraMeasure = new NoExtraMeasure();
                return Option.None<ExtraValue>();
            }
        }

        private Option<Gram> ReadBalanceOrDisable()
        {
            try
            {
                return this.balance.ReadValue();
            }
            catch (Exception e)
            {
                logger.Error("While reading balance: {0}. Disabling!", e.Message);
                this.balance = new NoBalance();
                return Option.None<Gram>();
            }
        }

        private Option<Pressure> ReadPressureOrDisable()
        {
            try
            {
                return this.pressureMeasure.ReadValue();
            }
            catch (Exception e)
            {
                logger.Error("While reading pressure measure: {0}. Disabling!", e.Message);
                this.pressureMeasure = new NoPressureMeasure();
                return Option.None<Pressure>();
            }
        }

        private Option<LiterPerHour> ReadGasFlowOrDisable()
        {
            try
            {
                return this.gasFlowMeasure.ReadValue();
            }
            catch (Exception e)
            {
                logger.Error("While reading gas-flow measure: {0}. Disabling!", e.Message);
                this.gasFlowMeasure = new NoGasFlowMeasure();
                return Option.None<LiterPerHour>();
            }
        }

        public VoegtlinDevice GetOrCreateVoegtlinDevice()
        {
            return scopedContainer.Resolve<VoegtlinDevice>();
        }

        public void Cleanup()
        {
            this.gasFlowMeasure.Cleanup();
            this.balance.Cleanup();
            this.pressureMeasure.Cleanup();
            this.extraMeasure.Cleanup();
            scopedContainer.Dispose();
        }
    }
}
