// 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.Ports;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Hiperscan.Unix;

namespace Hiperscan.Common.SerialPorts
{
    public partial class ComPort
    {
        public byte[] ReadBytes(int count)
        {
            try
            {
                byte[] buffer = new byte[count];
                int response = this.Port.Read(buffer, 0, count);

                if (response <= 0)
                    return null;

                byte[] response_buffer = new byte[response];
                Array.Copy(buffer, response_buffer, response);

                return response_buffer;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public byte? ReadByte()
        {
            try
            {
                int response = this.Port.ReadByte();
                if (response == -1)
                    return null;

                return (byte)response;
            }
            catch(Exception)
            {
                return null;
            }
        }

        public void SetReadTimeout(int timeout)
        {
            this.Port.ReadTimeout = timeout;
        }

        private void Listen()
        {
            this.ReadBuffer = new byte[this.Port.ReadBufferSize];

            this.Port.BaseStream.BeginRead(this.ReadBuffer,
                                           0,                   
                                           this.ReadBuffer.Length,                    
                                           this.AsyncCallback,                  
                                           null);
        }

        private void AsyncCallback(IAsyncResult ar)
        {
            lock (this)
            {
                try
                {
                    if (this.ListeningStopped)
                    {
                        this.SetInitialReadingState();
                        return;
                    }

                    System.Threading.Thread.Sleep(1);

                    if (this.ReadId % 2000000 == 0 && this.ReadId != 0)
                        this.ReadId = 0;
                    else
                        ++this.ReadId;


                    int number_received = this.Port.BaseStream.EndRead(ar);
                    byte[] received = new byte[number_received];

                    Buffer.BlockCopy(this.ReadBuffer, 0, received, 0, number_received);

                    if (this.Args == null)
                    {
                        this.State = ComPortState.Reading;
                        this.Args = new ComPort.ReceivedArgs(this.encoding);
                    }

                    this.Args.Add(received);
                    Thread thread = new Thread(this.CallDataReceived);
                    thread.Start(this.ReadId);

                    //System.Threading.Tasks.Task.Run(() => this.CallDataReceived(this.ReadId));
                    this.Listen();
                }
                catch (Exception ex)
                {
                    this.HandleError(ex, true);
                }
            }
        }

        private void CallDataReceived(object value)
        {
            int read_id = (int)value;

            System.Threading.Thread.Sleep(this.ReadBlockInterval);

            if (this.ReadId == read_id)
            {
                ReceivedArgs args = this.Args;
                this.SetInitialReadingState();

                if (args.Buffer.Length > 0)
                    this.DataReceived?.Invoke(this, args);
            }
        }

        private void SetInitialReadingState()
        {
            this.Args = null;
            this.State = ComPortState.Ready;
        }

        private Encoding encoding = Encoding.UTF8;
        public Encoding Encoding
        {
            get
            {
                return this.encoding;
            }
            set
            {
                lock (this)
                {
                    this.CheckDisosed();

                    if (value == null)
                        throw new Exception(Catalog.GetString("Encoding value must not be null."));

                    this.encoding = value;
                }
            }
        }

        private DateTime Start { get; set; } = DateTime.Now;

        private byte[] ReadBuffer { get; set; }
        private ReceivedArgs Args { get; set; }
        private bool ListeningStopped { get; set; } = true;
        private int ReadId { get; set; } = 0;
        public int ReadBlockInterval { get; set; } = 0;
        private int LastReadId { get; set; } = -1;
    }
}