using BoFilTest.Utils;
using Optional;
using System;
using System.Globalization;
using System.IO.Ports;
using System.Threading;

namespace BoFilTest.Domain.Modules.Balance
{
    public class SerialMettlerToledoBalance : IBalance
    {
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        private readonly SerialPort port;
        private readonly bool standByWhenDone;

        public static Gram ParseBalanceResponse(string input)
        {
            if (input.Length < 6)
                throw new HardwareException("Cannot parse balance response " + input);

            // Split of the header
            input = input.Substring(4);

            // Read until the gram
            var end = input.IndexOf('g');
            input = input.Substring(0, end).Trim();
            return Gram.From(double.Parse(input, CultureInfo.InvariantCulture));
        }

        public SerialMettlerToledoBalance(BalanceConfiguration config)
        : this(config.Port(), config.BaudRate(), config.Parity(),
              config.DataBits(), config.StopBits(), config.StandBy())
        {
        }

        public SerialMettlerToledoBalance(string port, int baudRate, Parity parity, int dataBits, StopBits stopBits, bool standByWhenDone)
        {
            this.standByWhenDone = standByWhenDone;
            this.port = new SerialPort(port,
                baudRate, parity, dataBits, stopBits);

            Start();
        }

        public void Cleanup()
        {
            logger.Info("Disconnecting balance");
            // Set the balance back into stand-by
            if (standByWhenDone)
            {
                port.WriteLine("PWR 0");
                var powerResponse = port.ReadLine();
                logger.Info("Balance power down: {0}", powerResponse);
            }
            port.Close();
        }

        public Option<Gram> ReadValue()
        {
            // Dump everything already read
            port.DiscardInBuffer();

            port.WriteLine("SI");
            var answer = port.ReadLine();
            logger.Info("Balance read: {0}", answer);

            try
            {

                return Option.Some(ParseBalanceResponse(answer));
            }
            catch(Exception e)
            {
                logger.Info("Error while reading balance value: {0}", e.Message);
                return Option.None<Gram>();
            }
        }

        private void Start()
        {
            port.NewLine = "\r\n";
            port.ReadTimeout = 1000;

            // Create the serial port with basic settings
            port.Open();

            // Try to wake the balance from stand-by
            port.WriteLine("PWR 1");
            Thread.Sleep(100);
            var powerResponse = port.ReadLine();
            logger.Info("Balance power up: {0}", powerResponse);
            if (powerResponse.StartsWith("PWR A"))
            {
                // Power was actually switched on
                // In this case, we'll get a second line with the serial number
                logger.Info("Balance power up (2): {0}", port.ReadLine());
            }
        }
    }
}
