// Created with MonoDevelop
//
//    Copyright (C) 2023 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.



using System;
using System.IO;
using System.IO.Ports;
using System.Text;

using Hiperscan.Unix;

namespace Hiperscan.Common.SerialPorts
{
    public partial class ComPort : IDisposable
    {
        public const int MAX_BLOCK_LENGTH = 1024 * 512;

        public delegate void ComPortEventHandler(object sender, ReceivedArgs e);

        public event ComPortEventHandler DataReceived = null;
        public event EventHandler ErrorThrown = null;

        public ComPort(string name, PortBaudRate baud_rate)
        {
            this.Name = name;
            this.BaudRate = (int)baud_rate;

            this.Port = new SerialPort()
            {
                BaudRate = this.BaudRate,
                PortName = this.Name,
                Parity = Parity.None,
                Handshake = Handshake.None,

                DtrEnable = true,
                RtsEnable = true,
                DataBits = 8,
                StopBits = StopBits.One,

                ReadTimeout = -1,
                WriteTimeout = -1,
            };

            this.Port.Open();
            this.State = ComPortState.Ready;
        }

        public void Clear(int delay = 500)
        {
            this.StopListening();
            System.Threading.Thread.Sleep(delay);

            lock (this)
            {
                for(; ; )
                {
                    int bytes_to_read = this.Port.BytesToRead;
                    if (bytes_to_read < 1)
                        break;

                    byte[] buffer = new byte[bytes_to_read];
                    this.Port.Read(buffer, 0, bytes_to_read);
                    System.Threading.Thread.Sleep(50);
                }
            }
        }

        public void StartListening()
        {
            lock (this)
            {
                this.CheckDisosed();

                try
                {
                    if (ListeningStopped == false)
                        return;

                    this.State = ComPortState.Ready;
                    this.ListeningStopped = false;
                    this.Listen();
                }
                catch (Exception ex)
                {
                    this.HandleError(ex, true);
                }
            }
        }

        public void StopListening()
        {
            lock (this)
            {
                this.CheckDisosed();
                this.ListeningStopped = true;
            }
        }

        private void HandleError(Exception ex, bool read_error)
        {
            if (read_error)
            {
                this.ListeningStopped = true;
                this.SetInitialReadingState();
                this.State = ComPortState.ReadError;
            }
            else
            {
                this.State = ComPortState.WriteError;
            }

            this.Exception = ex;
            System.Threading.Tasks.Task.Run(() => this.ErrorThrown?.Invoke(this, EventArgs.Empty));
        }

        public void Dispose()
        {
            lock(this)
            {
                this.CheckDisosed();
                this.ListeningStopped = true;
                try
                {
                    this.Port.BaseStream.Flush();
                    this.Port.BaseStream.Dispose();
                }
                catch(Exception) { }

                this.Port.Close();
                this.Port.Dispose();
                this.Port = null;
            }
        }

        private void CheckDisosed()
        {
            if (this.Port == null)
            {
                string message = Catalog.GetString("COM port is already disposed.");
                throw new Exception(message);
            }
        }

        public bool IsDisposed
        {
            get
            {
                lock (this)
                    return this.Port == null;
            }
        }

        public string Name { get; private set; }
        public int BaudRate { get; private set; }
        public ComPortState State { get; private set; } = ComPortState.None;
        public Exception Exception { get; private set; } = null;

        private SerialPort Port { get; set; }
    }
}
