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

namespace Elwig.Helpers.Weighing {
    public class AveryEventScale : Scale, IEventScale, IDisposable {

        public string Manufacturer => "Avery";
        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 event IEventScale.EventHandler<WeighingEventArgs>? WeighingEvent;

        private bool IsRunning = true;
        private readonly Thread BackgroundThread;
        private readonly string Connection;

        public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) :
            base(cnx, null, null, null, log, true, !required) {
            ScaleId = id;
            Model = model;
            Connection = cnx;
            IsReady = true;
            HasFillingClearance = false;
            Stream.WriteTimeout = -1;
            Stream.ReadTimeout = -1;
            BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
            BackgroundThread.Start();
        }

        protected virtual void RaiseWeighingEvent(WeighingEventArgs evt) {
            WeighingEvent?.Invoke(this, evt);
        }

        public new void Dispose() {
            IsRunning = false;
            BackgroundThread.Interrupt();
            BackgroundThread.Join();
            base.Dispose();
            GC.SuppressFinalize(this);
        }

        protected async void BackgroundLoop(object? parameters) {
            while (IsRunning) {
                try {
                    var data = await Receive();
                    if (data != null)
                        RaiseWeighingEvent(new WeighingEventArgs(data.Value));
                } catch (ThreadInterruptedException) {
                    // ignore
                } catch (IOException) {
                    await Task.Delay(500);
                    await Reconnect();
                } catch (TimeoutException) {
                    await Task.Delay(500);
                    await Reconnect();
                } catch (Exception ex) {
                    MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        protected async Task Reconnect() {
            try { Reader.Close(); } catch { }
            try { Stream.Close(); } catch { }
            try { Serial?.Close(); } catch { }
            while (IsRunning) {
                try {
                    if (Connection.StartsWith("serial:")) {
                        Serial = Utils.OpenSerialConnection(Connection);
                        Stream = Serial.BaseStream;
                    } else if (Connection.StartsWith("tcp:")) {
                        Tcp = Utils.OpenTcpConnection(Connection);
                        Stream = Tcp.GetStream();
                    }
                    Reader = new(Stream, Encoding.ASCII, false, 512);
                    break;
                } catch {
                    // ignore
                }
                await Task.Delay(1000);
            }
        }

        protected async Task<WeighingResult?> Receive() {
            var line = "";
            while (line.Length < 33) {
                var ch = Reader.Read();
                if (ch == -1) {
                    throw new IOException("Connection closed");
                } else if (line.Length > 0 || ch == ' ') {
                    line += char.ToString((char)ch);
                }
            }
            if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
            if (line == null || line == "") {
                return null;
            } else if (line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
                throw new FormatException($"Invalid event from scale: '{line}'");
            }

            var date    = line[ 1.. 9];
            var time    = line[10..15];
            var identNr = line[16..20].Trim();
            var netto   = line[21..30].Trim();
            var unit    = line[30..32];

            if (unit != "kg") {
                throw new WeighingException($"Unsupported unit in weighing event: '{unit}'");
            }

            identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
            var parsedDate = DateOnly.Parse(date);
            return new() {
                NetWeight = int.Parse(netto),
                WeighingId = identNr,
                FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null,
                Date = parsedDate,
                Time = TimeOnly.Parse(time),
            };
        }
    }
}
