using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace TcpToComPorts
{
    class Program
    {
        static List<TcpClient> tcpClients = new List<TcpClient>();
        static List<SerialPort> serialPorts = new List<SerialPort>();

        static void Main(string[] args)
        {

            var builder = new ConfigurationBuilder()
                   .SetBasePath(Directory.GetCurrentDirectory())
                   .AddJsonFile("settings.json", optional: false);
            var configuration = builder.Build();
            var tcpPortStrings = configuration.GetValue<string>("listeningTcpPorts").Split(',');
            var mapToComPortStrings = configuration.GetValue<string>("tcpPortsMapToComPorts").Split(',');
            if (tcpPortStrings.Length != mapToComPortStrings.Length)
            {
                Console.WriteLine("tcp ports count should equals to maptoCompPorts count");
                Console.ReadLine();
                return;
            }


            ThreadPool.QueueUserWorkItem(_ =>
            {
                for (int i = 0; i < mapToComPortStrings.Length; i++)
                {
                    int j = i;
                    SerialPort serialPort = new SerialPort("COM" + mapToComPortStrings[i],
                        9600, Parity.Odd, 8, StopBits.One);
                    serialPort.Open();
                    serialPorts.Add(serialPort);
                    serialPort.DataReceived += (s, a) =>
                    {
                        try
                        {
                            //if (tcpClients.Count >= (i + 1))
                            {
                                var buffer = new List<byte>();
                                while (serialPort.BytesToRead > 0)
                                {
                                    buffer.Add((byte)serialPort.ReadByte());
                                }

                                Console.WriteLine("Com" + serialPort.PortName + " to Tcp" + (tcpClients[j].Client.RemoteEndPoint as IPEndPoint).Port
                                    + ": 0x" + buffer.Select(b => b.ToString("X").PadLeft(2, '0')).Aggregate((acc, n) => acc + n));
                                tcpClients[j]?.GetStream().Write(buffer.ToArray());
                            }
                        }
                        catch (Exception exx)
                        {
                        }
                    };
                }

                for (int i = 0; i < tcpPortStrings.Length; i++)
                {
                    tcpClients.Add(null);
                    int tcpPort = int.Parse(tcpPortStrings[i]);
                    TcpListener l = new TcpListener(IPAddress.Any, tcpPort);
                    l.Start();
                    Console.WriteLine("Tcp listening port: " + tcpPort);
                    ReAcceptTcpClient(l, i);
                }
            });

            while (true)
                Console.ReadKey();
            Console.WriteLine("Hello World!");
        }

        static void ReAcceptTcpClient(TcpListener tcpListener, int index)
        {
            var result = tcpListener.BeginAcceptTcpClient((ar) =>
            {
                var listener = (TcpListener)ar.AsyncState;
                var tcpClient = listener.EndAcceptTcpClient(ar);
                tcpClients[index] = tcpClient;
                ReReadTcpData(tcpClient, index);
                ReAcceptTcpClient(tcpListener, index);
            }, tcpListener);
        }

        static void ReReadTcpData(TcpClient tcpClient, int index)
        {
            var buffer = new byte[2000];
            try
            {
                var r = tcpClient.GetStream().BeginRead(buffer, 0, 2000, (cb =>
                {
                    try
                    {
                        var readCount = tcpClient.Client.EndReceive(cb);
                        if (serialPorts.Count >= (index + 1))
                        {
                            Console.WriteLine("Tcp" + (tcpClient.Client.LocalEndPoint as IPEndPoint).Port
                                + " to Com" + serialPorts[index].PortName +
                                ": 0x" + buffer.Take(readCount).Select(b => b.ToString("X").PadLeft(2, '0')).Aggregate((acc, n) => acc + n));
                            serialPorts[index].Write(buffer, 0, readCount);
                        }
                    }
                    catch (Exception exxx)
                    {
                        Console.WriteLine("exception in BeginRead()");
                        return;
                    }

                    ReReadTcpData(tcpClient, index);
                })
                , tcpClient);
            }
            catch (Exception exxx)
            {
                Console.WriteLine("exception in ReReadTcpData()");
            }
        }
    }
}