using System.IO.Ports;
using System.IO;
using System.Net.Sockets;
using System;
using System.Text;

namespace Elwig.Helpers.Weighing {
    public abstract class Scale : IDisposable {

        protected enum Output { RTS, DTR, OUT1, OUT2 };

        protected SerialPort? Serial = null;
        protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
        protected TcpClient? Tcp = null;
        protected Stream Stream;
        protected StreamReader Reader;

        protected readonly Output? EmptyMode = null;
        protected readonly Output? FillingClearanceMode = null;
        protected readonly int EmptyDelay;

        public int? WeightLimit { get; private set; }
        public string? LogPath { get; private set; }

        public static IScale FromConfig(ScaleConfig config) {
            int? limit = config.Limit != null ? int.Parse(config.Limit) : null;
            if (config.Type == "SysTec-IT") {
                return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
            } else if (config.Type == "Avery-Async") {
                return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
            } else if (config.Type == "Gassner") {
                return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
            } else {
                throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
            }
        }

        protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) {
            if (cnx.StartsWith("serial:")) {
                Serial = Utils.OpenSerialConnection(cnx);
                Stream = Serial.BaseStream;
            } else if (cnx.StartsWith("tcp:")) {
                Tcp = Utils.OpenTcpConnection(cnx);
                Stream = Tcp.GetStream();
            } else {
                throw new ArgumentException($"Unsupported scheme: \"{cnx.Split(':')[0]}\"");
            }
            Reader = new(Stream, Encoding.ASCII, false, 512);

            LogPath = log;

            if (empty != null) {
                var parts = empty.Split(':');
                if (parts.Length == 3) {
                    if (parts[0] != Serial?.PortName)
                        ControlSerialEmpty = Utils.OpenSerialConnection($"serial://{parts[0]}:9600");
                } else if (parts.Length != 2) {
                    throw new ArgumentException("Invalid value for 'empty'");
                }
                EmptyMode = ConvertOutput(parts[^2]);
                EmptyDelay = int.Parse(parts[^1]);
            }

            WeightLimit = limit;
            if (filling != null) {
                var parts = filling.Split(':');
                if (parts.Length == 2) {
                    if (parts[0] != Serial?.PortName)
                        ControlSerialFilling = parts[0] != ControlSerialEmpty?.PortName ? Utils.OpenSerialConnection($"serial://{parts[0]}:9600") : ControlSerialEmpty;
                } else if (parts.Length != 1) {
                    throw new ArgumentException("Invalid value for 'filling'");
                }
                FillingClearanceMode = ConvertOutput(parts[^1]);
            }

            if (FillingClearanceMode != null && WeightLimit == null)
                throw new ArgumentException("Weight limit has to be set, if filling clearance supervision is enabled");
        }

        public void Dispose() {
            Reader.Close();
            Stream.Close();
            Serial?.Close();
            ControlSerialEmpty?.Close();
            ControlSerialFilling?.Close();
            Tcp?.Close();
            GC.SuppressFinalize(this);
        }

        protected static Output? ConvertOutput(string? value) {
            return value switch {
                null => null,
                "RTS" => Output.RTS,
                "DTR" => Output.DTR,
                "OUT1" => Output.OUT1,
                "OUT2" => Output.OUT2,
                _ => throw new ArgumentException($"Invalid value for argument: '{value}'"),
            };
        }
    }
}