using System;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;

namespace Elwig.Helpers.Weighing {
    public class GassnerScale : Scale, ICommandScale {

        public string Manufacturer => "Gassner";
        public int InternalScaleNr => 1;
        public string Model { get; private set; }
        public string ScaleId { get; private set; }
        public bool IsReady { get; private set; }
        public bool HasFillingClearance { get; private set; }

        public GassnerScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
          base(cnx, empty, filling, limit, log) {
            ScaleId = id;
            Model = model;
            IsReady = true;
            HasFillingClearance = false;
            Stream.WriteTimeout = 250;
            Stream.ReadTimeout = 11000;
        }

        protected Task SendCommand(char command) => SendCommand(Convert.ToByte(command));

        protected async Task SendCommand(byte command) {
            byte[] cmd = [command];
            await Stream.WriteAsync(cmd);
            if (LogPath != null) await File.AppendAllTextAsync(LogPath, Encoding.ASCII.GetString(cmd));
        }

        protected async Task<string> ReceiveResponse() {
            var line = await Reader.ReadUntilAsync('\x03');
            if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
            if (line == null || line.Length < 4 || !line.StartsWith('\x02')) {
                throw new IOException("Invalid response from scale");
            }

            var status = line[1..3];
            if (status[0] == 'E' || status[1] != 'S') {
                string msg = $"Unbekannter Fehler (Fehler code {status})";
                switch (status[1]) {
                    case 'M': msg = "Waage in Bewegung"; break;
                }
                throw new IOException($"Waagenfehler {status}: {msg}");
            } else if (status[0] != ' ') {
                throw new IOException($"Invalid response from scale (error code {status})");
            }

            return line[1..^1];
        }

        protected async Task<WeighingResult> Weigh(bool incIdentNr) {
            await SendCommand(incIdentNr ? '\x05' : '?');
            string record = await ReceiveResponse();
            if (record.Length != 45)
                throw new IOException("Invalid response from scale: Received record has invalid size");
            var line = record[2..];

            var brutto  = line[ 0.. 7].Trim();
            var tara    = line[ 7..14].Trim();
            var netto   = line[14..21].Trim();
            var scaleNr = line[21..23].Trim();
            var identNr = line[23..29].Trim();
            var date    = line[29..37];
            var time    = line[37..43];

            identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
            return new() {
                GrossWeight = int.Parse(brutto),
                TareWeight = int.Parse(tara),
                NetWeight = int.Parse(netto),
                WeighingId = identNr,
                FullWeighingId = identNr,
                Date = DateOnly.TryParseExact(date, "yyyyMMdd", out var d) ? d : null,
                Time = TimeOnly.TryParseExact(time, "HHmmss", out var t) ? t : null,
            };
        }

        public async Task<WeighingResult> Weigh() {
            return await Weigh(true);
        }

        public async Task<WeighingResult> GetCurrentWeight() {
            return await Weigh(false);
        }

        public async Task Empty() {
            SerialPort? p = ControlSerialEmpty ?? Serial;
            if (EmptyMode == Output.RTS && p != null) {
                p.RtsEnable = true;
                await Task.Delay(EmptyDelay);
                p.RtsEnable = false;
            } else if (EmptyMode == Output.DTR && p != null) {
                p.DtrEnable = true;
                await Task.Delay(EmptyDelay);
                p.DtrEnable = false;
            }
        }

        protected Task SetFillingClearance(bool status) {
            SerialPort? p = ControlSerialFilling ?? Serial;
            if (FillingClearanceMode == Output.RTS && p != null) {
                p.RtsEnable = status;
            } else if (FillingClearanceMode == Output.DTR && p != null) {
                p.DtrEnable = status;
            }
            return Task.CompletedTask;
        }

        public async Task GrantFillingClearance() {
            await SetFillingClearance(true);
        }

        public async Task RevokeFillingClearance() {
            await SetFillingClearance(false);
        }
    }
}