using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using skyscraper5.IO.StreamReader;
using skyscraper5.Skyscraper.IO.RemoteStreamReader;
using skyscraper5.Skyscraper.Net.Sockets;
using skyscraper5.src.Skyscraper.IO.PortableExecutables;

namespace skyscraper5.Skyscraper.IO.CrazycatStreamReader
{

	internal class RemoteStreamReaderServer
    {
        private bool _logRequests;
        private int _listenPort;
        private IPAddress _listenAddress;
        private bool clientConnected;
        private IStreamReader localStreamReader;
        private Thread clientThread;
        private Dictionary<string, string> dllVersionInfo;

        public bool IsRunning { get; private set; }

        public int ListenPort
        {
            get => _listenPort;
            set
            {
                if (IsRunning)
                    throw new InvalidOperationException();
                _listenPort = value;
            }
        }

        public IPAddress ListenAddress
        {
            get => _listenAddress;
            set
            {
                if (IsRunning)
                    throw new InvalidOperationException();
                _listenAddress = value;
            }
        }

        public bool LogRequests
        {
            get => _logRequests;
            set => _logRequests = value;
        }

        public void Run()
        {			
            if (localStreamReader == null)
            {
                localStreamReader = LocalStreamReader.GetInstance();
                if (!localStreamReader.CheckForDVB())
                {
                    Console.WriteLine("No tuners present, quitting.");
                    return;
                }
            }

            if (ListenAddress == null)
                ListenAddress = IPAddress.Any;

            IPEndPoint listenEndPoint = new IPEndPoint(ListenAddress, ListenPort);
            TcpListener tcpListener = new TcpListener(listenEndPoint);
            tcpListener.Start();
            Console.WriteLine("Listening on {0}", tcpListener.LocalEndpoint);

            while (true)
            {
                TcpClient acceptTcpClient = tcpListener.AcceptTcpClient();
                if (clientConnected)
                {
                    Console.WriteLine("Rejected connection from {0}. I'm busy.", acceptTcpClient.Client.RemoteEndPoint);
                    NetworkStream blockoffStream = acceptTcpClient.GetStream();
                    blockoffStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.SERVER_BUSY_WITH_ANOTHER_CLIENT);
                    blockoffStream.Flush();
                    acceptTcpClient.Close();
                }
                else
                {
                    Console.WriteLine("Accepted connection from {0}", acceptTcpClient.Client.RemoteEndPoint);
                    clientConnected = true;
                    clientThread = new Thread(ClientThreadMethod);
                    clientThread.Name = String.Format("RemoteStreamReader Client @ {0}", acceptTcpClient.Client.RemoteEndPoint);
                    clientThread.Start(acceptTcpClient);
                }
            }
        }

        private void ClientThreadMethod(object o)
        {
            TcpClient tcpClient = (TcpClient)o;
            NetworkStream tcpStream = tcpClient.GetStream();
            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.SERVER_READY);
            tcpStream.Flush();
            bool breakout = false;
            while (!breakout)
            {
                uint opcode;
                try
                {
                    opcode = tcpStream.ReadUInt32BE();
                    if (LogRequests)
                        Console.WriteLine("Request from {0}: {1}", tcpClient.Client.RemoteEndPoint, (RemoteStreamReaderConstants)opcode);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Lost connection to {0}: {1}", tcpClient.Client.RemoteEndPoint, e);
                    clientConnected = false;
                    return;
                }
                switch (opcode)
                {
                    case (uint)RemoteStreamReaderConstants.REQUEST_NOOP:
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_HOSTNAME:
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.WriteUTF8(Environment.MachineName);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_USERNAME:
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.WriteUTF8(Environment.UserName);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_DATE_TIME:
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.WriteInt64BE(DateTime.Now.Ticks);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_DISPOSE:
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.Flush();
                        breakout = true;
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB_EX_EX:
                        bool checkForDvbExEx = localStreamReader.CheckForDVBExEx((index, name, type) =>
                        {
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.ENUMERATE_CHECK_FOR_DVB_EX_EX);
                            tcpStream.WriteInt32BE(index);
                            tcpStream.WriteUTF8(name);
                            tcpStream.WriteInt32BE((int)type);
                        });
                        if (checkForDvbExEx)
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        else
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_START_DVB_EX:
                        int readIndex = tcpStream.ReadInt32BE();
                        bool startDvbExResult = localStreamReader.StartDvbEx(readIndex);
                        tcpStream.WriteUInt32BE(startDvbExResult
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_STOP_DVB:
                        bool stopDvbResult = localStreamReader.StopDVB();
                        tcpStream.WriteUInt32BE(stopDvbResult
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_GET_TUNER_TYPE:
                        STD_TYPE m = (STD_TYPE)(-1);
                        bool getTunerTypeResult = localStreamReader.GetTunerType(ref m);
                        tcpStream.WriteUInt32BE(getTunerTypeResult
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        if (getTunerTypeResult)
                        {
                            tcpStream.WriteInt32BE((int)m);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_GET_CAPS:
                        Caps caps = localStreamReader.GetCaps();
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.WriteInt32BE((int)caps);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SEND_DISEQC:
                        uint diseqcType = tcpStream.ReadUInt32BE();
                        byte recvMe = tcpStream.ReadUInt8();
                        DiSEqC_Opcode diSEqCOpcode = (DiSEqC_Opcode)recvMe;
                        bool sendDiSEqC = localStreamReader.SendDiSEqC(diseqcType, diSEqCOpcode);
                        tcpStream.WriteUInt32BE(sendDiSEqC
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_RF_SCAN:
                        int rfScanFreq = tcpStream.ReadInt32BE();
                        int rfScanPol = tcpStream.ReadInt32BE();
                        int rfScanLof1 = tcpStream.ReadInt32BE();
                        int rfScanLof2 = tcpStream.ReadInt32BE();
                        int rfScanLofSw = tcpStream.ReadInt32BE();
                        double rfScanRfLevel = Double.NaN;
                        bool rfScan = localStreamReader.RFScan(rfScanFreq, rfScanPol, rfScanLof1, rfScanLof2, rfScanLofSw, out rfScanRfLevel);
                        tcpStream.WriteUInt32BE(rfScan
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.WriteDouble(rfScanRfLevel);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL:
                        int setChannelFreq = tcpStream.ReadInt32BE();
                        int setChannelSymbRate = tcpStream.ReadInt32BE();
                        int setChannelPol = tcpStream.ReadInt32BE();
                        VITERBIRATE_TYPE setChannelFec = (VITERBIRATE_TYPE)tcpStream.ReadInt32BE();
                        int setChannelLof1 = tcpStream.ReadInt32BE();
                        int setChannelLof2 = tcpStream.ReadInt32BE();
                        int setChannelLofSw = tcpStream.ReadInt32BE();
                        bool channel = localStreamReader.SetChannel(setChannelFreq, setChannelSymbRate, setChannelPol, setChannelFec, setChannelLof1, setChannelLof2, setChannelLofSw);
                        tcpStream.WriteUInt32BE(channel
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO:
                        SearchResult signalInfoSearchResult = default;
                        bool signalInfo = localStreamReader.SignalInfo(ref signalInfoSearchResult);
                        tcpStream.WriteUInt32BE(signalInfo
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        if (signalInfo)
                        {
                            int sizeOf = Marshal.SizeOf(typeof(SearchResult));
                            IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);
                            Marshal.StructureToPtr(signalInfoSearchResult, allocHGlobal, true);
                            byte[] bytes = new byte[sizeOf];
                            Marshal.Copy(allocHGlobal, bytes, 0, sizeOf);
                            Marshal.FreeHGlobal(allocHGlobal);
                            tcpStream.Write(bytes, 0, sizeOf);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SET_FILTER:
                        int setFilterPid = tcpStream.ReadInt32BE();
                        int setFilterSize = tcpStream.ReadInt32BE();
                        FilterServer filterServer = new FilterServer();
                        IntPtr setFilterNumber = default;
                        bool filter = localStreamReader.SetFilter(setFilterPid, filterServer.DvbCallback, 0x02, setFilterSize, ref setFilterNumber);
                        tcpStream.WriteUInt32BE(filter
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        if (filter)
                        {
                            if (filterServers == null)
                                filterServers = new Dictionary<IntPtr, FilterServer>();
                            filterServers.Add(setFilterNumber, filterServer);
                            filterServer.ListenIp = ListenAddress;
                            filterServer.TcpListener = new TcpListener(filterServer.ListenIp, 0);
                            filterServer.TcpListener.Start();
                            filterServer.Thread = new Thread(filterServer.Run);
                            string filterName = String.Format("Filter for PID {0} @{1}", setFilterPid, filterServer.TcpListener.Server.LocalEndPoint);
                            filterServer.Thread.Name = filterName;
                            filterServer.Thread.Start();
                            tcpStream.WriteIntPtr(setFilterNumber);
                            tcpStream.WriteIPEndPoint((IPEndPoint)filterServer.TcpListener.LocalEndpoint);
                            Console.WriteLine(filterName);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_DEL_FILTER:
                        IntPtr delFilterNumber = tcpStream.ReadIntPtr();
                        bool delFilter = localStreamReader.DelFilter(delFilterNumber);
                        tcpStream.WriteUInt32BE(delFilter
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        if(delFilter)
                        {
                            FilterServer server = filterServers[delFilterNumber];
                            lock (server.clients)
                            {
                                server.clients.ForEach(x => x.Close());
                            }
                            server.Dispose();
                            server.Thread.Interrupt();
                            filterServers.Remove(delFilterNumber);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN2:
                        int blscan2FreqStart = tcpStream.ReadInt32BE();
                        int blscan2FreqStop = tcpStream.ReadInt32BE();
                        int blscan2Pol = tcpStream.ReadInt32BE();
                        int blscan2Lof1 = tcpStream.ReadInt32BE();
                        int blscan2Lof2 = tcpStream.ReadInt32BE();
                        int blscan2LofSw = tcpStream.ReadInt32BE();
                        IntPtr blscan2SearchResults = Marshal.AllocHGlobal(65536);
                        int blscan2TpNum = -1;
                        BlScanCallback blScanCallback = (ref SearchResult result) =>
                        {
                            int sizeOf = Marshal.SizeOf(typeof(SearchResult));
                            IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);
                            Marshal.StructureToPtr(result, allocHGlobal, true);
                            byte[] buffer = new byte[sizeOf];
                            Marshal.Copy(allocHGlobal, buffer, 0, sizeOf);
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.ENUMERATE_BL_SCAN_2);
                            tcpStream.Write(buffer, 0, sizeOf);
                            Marshal.FreeHGlobal(allocHGlobal);
                        };
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME);
                        bool blScan2 = localStreamReader.BLScan2(blscan2FreqStart, blscan2FreqStop, blscan2Pol, blscan2Lof1, blscan2Lof2, blscan2LofSw, blscan2SearchResults, ref blscan2TpNum, blScanCallback);
                        tcpStream.WriteUInt32BE(blScan2
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        if (blScan2)
                        {
                            tcpStream.WriteInt32BE(blscan2TpNum);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB:
                        bool checkForDvb = localStreamReader.CheckForDVB();
                        if (checkForDvb)
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        else
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_GET_MAC:
                        byte[] buffer = new byte[6];
                        bool mac = localStreamReader.GetMAC(buffer);
                        if (mac)
                        {
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                            tcpStream.Write(buffer, 0, 6);
                        }
                        else
                        {
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_AIR_SCAN:
                        int freqStart = tcpStream.ReadInt32BE();
                        int freqEnd = tcpStream.ReadInt32BE();
                        uint step = tcpStream.ReadUInt32BE();
                        uint bandwidth = tcpStream.ReadUInt32BE();
                        int std = tcpStream.ReadInt32BE();
                        IntPtr airScanSearchResults = Marshal.AllocHGlobal(65536);
                        int airScanTpNum = -1;
                        AirScanCallback airScanCallback = (ref SearchResult2 result) =>
                        {
                            int sizeOf = Marshal.SizeOf(typeof(SearchResult2));
                            IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);
                            Marshal.StructureToPtr(result, allocHGlobal, true);
                            byte[] buffer = new byte[sizeOf];
                            Marshal.Copy(allocHGlobal, buffer, 0, sizeOf);
                            tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.ENUMERATE_AIR_SCAN);
                            tcpStream.Write(buffer, 0, sizeOf);
                            Marshal.FreeHGlobal(allocHGlobal);
                        };
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME);
                        bool airscan = localStreamReader.AirScan(freqStart, freqEnd, step, bandwidth, std, airScanSearchResults, ref airScanTpNum, airScanCallback);
                        tcpStream.WriteUInt32BE(airscan
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        if (airscan)
                        {
                            tcpStream.WriteInt32BE(airScanTpNum);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL_2:
                        uint freq = tcpStream.ReadUInt32BE();
                        uint bandwidth2 = tcpStream.ReadUInt32BE();
                        bool channel2 = localStreamReader.SetChannel2(freq, bandwidth2);
                        tcpStream.WriteUInt32BE(channel2
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO_2:
                        SearchResult2 signalInfoSearchResult2 = default;
                        bool signalInfo2 = localStreamReader.SignalInfo2(ref signalInfoSearchResult2);
                        tcpStream.WriteUInt32BE(signalInfo2
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        if (signalInfo2)
                        {
                            int sizeOf = Marshal.SizeOf(typeof(SearchResult2));
                            IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);
                            Marshal.StructureToPtr(signalInfoSearchResult2, allocHGlobal, true);
                            byte[] bytes = new byte[sizeOf];
                            Marshal.Copy(allocHGlobal, bytes, 0, sizeOf);
                            Marshal.FreeHGlobal(allocHGlobal);
                            tcpStream.Write(bytes, 0, sizeOf);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_MIS_SEL:
                        bool bEnable = tcpStream.ReadBoolean();
                        byte misFilter = tcpStream.ReadUInt8();
                        byte misFilterMask = tcpStream.ReadUInt8();
                        bool misSel = localStreamReader.MISSel(bEnable, misFilter, misFilterMask);
                        tcpStream.WriteUInt32BE(misSel
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_PLS_SEL:
                        byte plsMode = tcpStream.ReadUInt8();
                        uint code = tcpStream.ReadUInt32BE();
                        bool plsSel = localStreamReader.PLSSel(plsMode, code);
                        tcpStream.WriteUInt32BE(plsSel
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN_EX:
                        freq = tcpStream.ReadUInt32BE();
                        int freq_range = tcpStream.ReadInt32BE();
                        int pol = tcpStream.ReadInt32BE();
                        int lof1 = tcpStream.ReadInt32BE();
                        int lof2 = tcpStream.ReadInt32BE();
                        int lofsw = tcpStream.ReadInt32BE();
                        int minsr = tcpStream.ReadInt32BE();
                        std = tcpStream.ReadInt32BE();
                        
                        SearchResult result = new SearchResult();
                        bool blScanEx = localStreamReader.BLScanEx((int)freq, freq_range, pol, lof1, lof2, lofsw, minsr, (STD_TYPE)std, ref result);
                        tcpStream.WriteUInt32BE(blScanEx
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);

                        if (blScanEx)
                        {
                            int sizeOfEx = Marshal.SizeOf(typeof(SearchResult));
                            IntPtr allocHGlobalEx = Marshal.AllocHGlobal(sizeOfEx);
                            Marshal.StructureToPtr(result, allocHGlobalEx, true);
                            byte[] bufferEx = new byte[sizeOfEx];
                            Marshal.Copy(allocHGlobalEx, bufferEx, 0, sizeOfEx);
                            tcpStream.Write(bufferEx, 0, sizeOfEx);
                            Marshal.FreeHGlobal(allocHGlobalEx);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_MOD_SEL:
                        S2Mode s2mode = new S2Mode();
                        s2mode.frameLen = tcpStream.ReadInt32BE();
                        s2mode.modcode = (S2MODCODE)tcpStream.ReadInt32BE();
                        s2mode.pilot = tcpStream.ReadBoolean();
                        uint num = tcpStream.ReadUInt32BE();

                        bool modSel = localStreamReader.ModSel(ref s2mode, num);
                        tcpStream.WriteUInt32BE(modSel
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);

                        if (modSel)
                        {
                            tcpStream.WriteInt32BE(s2mode.frameLen);
                            tcpStream.WriteInt32BE((int)s2mode.modcode);
                            tcpStream.WriteBoolean(s2mode.pilot);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_IQ_SCAN:
                        uint input = tcpStream.ReadUInt32BE();
                        sbyte[] pIQ;
                        num = tcpStream.ReadUInt32BE();
                        pIQ = new sbyte[num * 2];

                        bool iqScan = localStreamReader.IQScan(input, pIQ, num);
                        tcpStream.WriteUInt32BE(iqScan
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);

                        if (iqScan)
                        {
                            byte[] pIQunsigned = new byte[num * 2];
                            for (long i = 0; i < num * 2; i++)
                            {
                                pIQunsigned[i] = (byte)pIQ[i];
                            }
                            tcpStream.WriteByteArray(pIQunsigned);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_GET_SIGNAL_EX_EX:
                        bool pPresent = false;
                        bool pLock = false;
                        int pRFLevel = int.MinValue;
                        float pSNR = float.NaN;
                        float pBER = float.NaN;

                        bool getSignalExEx = localStreamReader.GetSignalExEx(ref pPresent, ref pLock, ref pRFLevel, ref pSNR, ref pBER);
                        tcpStream.WriteUInt32BE(getSignalExEx
                            ? (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL
                            : (uint)RemoteStreamReaderConstants.COMMAND_FAILED);

                        if (getSignalExEx)
                        {
                            tcpStream.WriteBoolean(pPresent);
                            tcpStream.WriteBoolean(pPresent);
                            tcpStream.WriteInt32BE(pRFLevel);
                            tcpStream.WriteFloat(pSNR);
                            tcpStream.WriteFloat(pBER);
                        }
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_NAME:
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.WriteUTF8(localStreamReader.GetEngineName());
                        tcpStream.Flush();
                        break;
                    case (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_VERSION:
						tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL);
                        tcpStream.WriteVersion(localStreamReader.GetEngineVersion());
                        tcpStream.Flush();
                        break;
					default:
                        Console.WriteLine("Recieved unknown command {0:X8}", opcode);
                        tcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.COMMAND_NOT_UNDERSTOOD);
                        tcpStream.Flush();
                        break;
                }
            }
            tcpStream.Close();
            tcpClient.Close();
            clientConnected = false;
        }

        private Dictionary<IntPtr, FilterServer> filterServers;
        class FilterServer : IDisposable
        {
            public FilterServer()
            {
                clients = new List<TcpClientWrapped>();
            }

            private byte[] buffer;
            public void DvbCallback(IntPtr pointer, int length)
            {
                if (buffer == null)
                    buffer = new byte[188];

                Marshal.Copy(pointer, buffer, 0, 188);

                lock (clients)
                {
                    foreach (TcpClientWrapped tcpClient in clients)
                    {
                        try
                        {
                            tcpClient.DropPacket(buffer, 0, 188);
                        }
                        catch (Exception e)
                        {
                            if (tcpClient.wrapped.Client != null)
                            {
                                Console.WriteLine("Lost filter client: {0}", tcpClient.wrapped.Client.RemoteEndPoint);
                            }

                            clients.Remove(tcpClient);
                            break;
                        }
                    }
                }
            }

            public IPAddress ListenIp { get; set; }
            public Thread Thread { get; set; }
            public TcpListener TcpListener { get; set; }
            
            public List<TcpClientWrapped> clients;

            public class TcpClientWrapped : IDisposable
            {
                public TcpClientWrapped(TcpClient client)
                {
                    this.wrapped = client;
                    this.bufferedStream = new BufferedStream(wrapped.GetStream(), 96256);
                }
                internal TcpClient wrapped;
                internal BufferedStream bufferedStream;

                public void Dispose()
                {
                    wrapped?.Dispose();
                    bufferedStream?.Dispose();
                }

                public void Close()
                {
                    try
                    {
                        bufferedStream?.Close();
                        wrapped?.Close();
                    }
                    catch (IOException ioe)
                    {
                        Console.WriteLine("Failed to properly close TCPClient");
                    }
                }

				internal void DropPacket(byte[] buffer, int offset, int length)
				{
                    bufferedStream?.Write(buffer, offset, length);
				}
			}


            public void Run()
            {
                while (!disposed)
                {
                    try
                    {
                        TcpClient acceptTcpClient = TcpListener.AcceptTcpClient();
                        Console.WriteLine("Accepted Filter client {0}", acceptTcpClient.Client.RemoteEndPoint);
                        lock (clients)
                        {
                            clients.Add(new TcpClientWrapped(acceptTcpClient));
                        }
                    }
                    catch (Exception e)
                    {
                        break;
                    }
                }

                if (!disposed)
                    Dispose();
            }

            private bool disposed;
            public void Dispose()
            {
                TcpListener.Stop();
                disposed = true;
            }
        }
    }
}
