using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper5.Skyscraper.Net.Sockets;

namespace skyscraper5.Skyscraper.IO.RemoteStreamReader
{
    public class RemoteStreamReaderClient : IDisposable, IStreamReader
    {
        public RemoteStreamReaderClient(IPEndPoint remote)
        {
            this.remoteEndPoint = remote;
            this.Reconnect();
        }

        private IPEndPoint remoteEndPoint;

        private void Reconnect()
        {
            if (remoteEndPoint == null)
            {
                if (this.TcpStream != null)
                {
                    this.remoteEndPoint = (IPEndPoint)this.TcpStream.Socket.RemoteEndPoint;
                }
            }
			TcpClient tcpClient = new TcpClient();
			tcpClient.Connect(remoteEndPoint);

			TcpClient = tcpClient;
			TcpStream = tcpClient.GetStream();

			RemoteStreamReaderConstants greeting = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (greeting)
            {
                case RemoteStreamReaderConstants.SERVER_READY:
                    return;
                case RemoteStreamReaderConstants.DENIED_UNKNOWN_ADDRESS_FAMILY:
                    throw new RemoteStreamReaderException("The remote server says that this client is using an invalid Network adressing family. Please ensure that IPv4 and/or IPv6 are available on both the server and the client.");
                case RemoteStreamReaderConstants.DENIED_EXTERNAL_ACCESS:
                    throw new RemoteStreamReaderException("The remote server thinks that this client is on another network than itself. For security purposes, RemoteStreamReader is not routeable.");

            }
			if (greeting != RemoteStreamReaderConstants.SERVER_READY)
			{
				tcpClient.Close();
				throw new RemoteStreamReaderException("Server is either busy or there is a protocol violation.");
			}
		}

        public NetworkStream TcpStream { get; set; }

        public TcpClient TcpClient { get; set; }

        public bool Disposed { get; private set; }

        public void Dispose()
        {
            if (TcpStream != null)
            {
                NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_DISPOSE);
                TcpStream.Flush();
                RemoteStreamReaderConstants dispoResponse = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
                if (dispoResponse != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL)
                {
                    Console.WriteLine("Warning: Server didn't dispose correctly.");
                }
                TcpStream.Dispose();
            }
            TcpClient?.Dispose();
            Disposed = true;
        }

        public void NoOperation()
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_NOOP);
            TcpStream.Flush();
            RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL)
                throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(NoOperation)));
        }

        public string GetHostname()
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_HOSTNAME);
            TcpStream.Flush();
            RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL)
                throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(GetHostname)));

            string result = TcpStream.ReadUTF8();
            return result;
        }

        public string GetUsername()
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_USERNAME);
            TcpStream.Flush();
            RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL)
                throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(GetUsername)));

            string result = TcpStream.ReadUTF8();
            return result;
        }

        public DateTime GetDateTime()
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_DATE_TIME);
            TcpStream.Flush();
            RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            if (response != RemoteStreamReaderConstants.COMMAND_SUCCESSFUL)
                throw new RemoteStreamReaderException(string.Format("{0} failed.", nameof(GetUsername)));

            long int64 = TcpStream.ReadInt64BE();
            return new DateTime(int64);
        }

        public bool CheckForDVB()
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB);
            TcpStream.Flush();

            RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (response)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(response.ToString());
            }
        }

        public bool CheckForDVBEx(DvbEnumCallback func)
        {
            throw new NotImplementedException();
        }

        public bool CheckForDVBExEx(DvbEnumCallbackEx func)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_CHECK_FOR_DVB_EX_EX);
            TcpStream.Flush();

            while (true)
            {
                uint opcode = TcpStream.ReadUInt32BE();
                switch (opcode)
                {
                    case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                        return true;
                    case (uint)RemoteStreamReaderConstants.COMMAND_FAILED:
                        return false;
                    case (uint)RemoteStreamReaderConstants.ENUMERATE_CHECK_FOR_DVB_EX_EX:
                        int index = TcpStream.ReadInt32BE();
                        string name = TcpStream.ReadUTF8();
                        STD_TYPE type = (STD_TYPE)TcpStream.ReadInt32BE();
                        func(index, name, type);
                        break;
                    default:
                        throw new RemoteStreamReaderException(string.Format("Protocol violation in {0}", nameof(CheckForDVBExEx)));
                }
            }
        }

        public bool StartDVB()
        {
            throw new NotImplementedException();
        }

        public bool StartDvbEx(int index)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_START_DVB_EX);
            TcpStream.WriteInt32BE(index);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool StopDVB()
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_STOP_DVB);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool GetTunerType(ref STD_TYPE type)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_TUNER_TYPE);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    type = (STD_TYPE)TcpStream.ReadInt32BE();
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool SendDiSEqC(uint diseqcType, DiSEqC_Opcode data)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SEND_DISEQC);
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, diseqcType);
            byte sendMe = (byte)data;
            NetworkStreamExtensions.WriteUInt8(TcpStream, sendMe);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool SendDiseqCmd(byte[] buffer, int length)
        {
            throw new NotImplementedException();
        }

        public bool SendDiseqCmdEx(IntPtr pCmd, int length, IntPtr reply, int replyLength)
        {
            throw new NotImplementedException();
        }

        public bool SetChannel(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL);
            TcpStream.WriteInt32BE(freq);
            TcpStream.WriteInt32BE(symbrate);
            TcpStream.WriteInt32BE(pol);
            TcpStream.WriteInt32BE((int)fec);
            TcpStream.WriteInt32BE(lof1);
            TcpStream.WriteInt32BE(lof2);
            TcpStream.WriteInt32BE(lofsw);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool SetChannelEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod)
        {
            throw new NotImplementedException();
        }

        public bool SetChannelExEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod,
            int inv, int pilot, ROLLOFF_TYPE rolloff)
        {
            throw new NotImplementedException();
        }

        public bool SetFilter(int pid, StdcallDvbCallback func, int callbackType, int size, ref IntPtr lpFilterNum)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SET_FILTER);
            TcpStream.WriteInt32BE(pid);
            TcpStream.WriteInt32BE(size);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    lpFilterNum = TcpStream.ReadIntPtr();
                    IPEndPoint remoteListener = TcpStream.ReadIPEndPoint();
                    IPEndPoint masterEndPoint = TcpClient.Client.RemoteEndPoint as IPEndPoint;
                    IPEndPoint streamSource = new IPEndPoint(masterEndPoint.Address, remoteListener.Port);
                    if (filterClients == null)
                        filterClients = new Dictionary<IntPtr, FilterClient>();
                    FilterClient childClient = new FilterClient(streamSource, func);
                    filterClients.Add(lpFilterNum, childClient);
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        private Dictionary<IntPtr, FilterClient> filterClients;
        class FilterClient : IDisposable
        {
            private readonly StdcallDvbCallback _stdcallDvbCallback;
            private readonly TcpClient _tcpClient;
            private readonly BufferedStream _bufferedStream;
            public Thread Thread { get; private set; }
            private readonly NetworkStream _tcpStream;

            public FilterClient(IPEndPoint streamSource, StdcallDvbCallback stdcallDvbCallback)
            {
                _stdcallDvbCallback = stdcallDvbCallback;
                _tcpClient = new TcpClient();
                _tcpClient.Connect(streamSource);
                _tcpStream = _tcpClient.GetStream();
                _bufferedStream = new BufferedStream(_tcpStream, 96256);
                Thread = new Thread(Run);
                Thread.Name = string.Format("Remote Filter @{0}", streamSource.ToString());
                Thread.Start();
            }

            private bool disposed;
            public void Dispose()
            {
                disposed = true;
                _tcpClient?.Dispose();
            }

            private byte[] safeBuffer;
            private IntPtr unsafeBuffer;

            private void Run()
            {
                safeBuffer = new byte[188];
                unsafeBuffer = Marshal.AllocHGlobal(188);
                while (!disposed)
                {
                    try
                    {
                        if (_bufferedStream.Read(safeBuffer, 0, 188) != 188)
                            throw new IOException("omg wut?");
                    }
                    catch (NotSupportedException e)
                    {
                        //Disposal race condition.
                        continue;
                    }
                    catch (ObjectDisposedException e)
                    {
                        break;
                    }
                    catch (IOException e)
                    {
                        break;
                    }
                    catch (ThreadInterruptedException tie)
                    {
                        break;
                    }


                    Marshal.Copy(safeBuffer, 0, unsafeBuffer, 188);
                    _stdcallDvbCallback(unsafeBuffer, 188);
                }

                Marshal.FreeHGlobal(unsafeBuffer);

                if (!disposed)
                    Dispose();
            }
        }

        public bool SetFilterEx(int pid, StdcallDvbCallbackEx lpFunc, int callbackType, int size, ref IntPtr lpFilterNum)
        {
            throw new NotImplementedException();
        }

        public bool SetBitFilter(int pid, IntPtr filterData, IntPtr filterMask, byte filterLength, StdcallDvbCallback lpFunc,
            ref IntPtr lpFilterNum)
        {
            throw new NotImplementedException();
        }

        public bool SetRemoteControl(int irtype, short devAddr, StdcallDvbCallback func, ref IntPtr lpFilterNum)
        {
            throw new NotImplementedException();
        }

        public bool DelFilter(IntPtr filterNum)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_DEL_FILTER);
            TcpStream.WriteIntPtr(filterNum);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    filterClients[filterNum].Dispose();
                    filterClients[filterNum].Thread.Interrupt();
                    filterClients.Remove(filterNum);
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool GetSignal(ref int pStrength, ref int pQuality)
        {
            throw new NotImplementedException();
        }

        public bool GetSignalStrength(ref float pStrength, ref float pQuality)
        {
            throw new NotImplementedException();
        }

        public bool GetSignalEx(ref float pSNR, ref float pBER)
        {
            throw new NotImplementedException();
        }

        public bool GetSignalExEx(ref bool pPresent, ref bool pLock, ref int pRFLevel, ref float pSNR, ref float pBER)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_SIGNAL_EX_EX);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    pPresent = TcpStream.ReadBoolean();
                    pLock = TcpStream.ReadBoolean();
                    pRFLevel = TcpStream.ReadInt32BE();
                    pSNR = TcpStream.ReadFloatBE();
                    pBER = TcpStream.ReadFloatBE();
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool Statistic(int[] pStat)
        {
            throw new NotImplementedException();
        }

        public bool GetMAC(byte[] pMac)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_MAC);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    if (TcpStream.Read(pMac, 0, 6) != 6)
                        throw new EndOfStreamException();
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public Caps GetCaps()
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_GET_CAPS);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    Caps caps = (Caps)TcpStream.ReadInt32BE();
                    return caps;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool RFScan(int freq, int pol, int lof1, int lof2, int lofsw, out double pRFLevel)
        {
	        byte[] cmdBuffer = new byte[24];
	        MemoryStream cmdStream = new MemoryStream(cmdBuffer, 0, 24, true);
            cmdStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.REQUEST_RF_SCAN);
            cmdStream.WriteInt32BE(freq);
            cmdStream.WriteInt32BE(pol);
            cmdStream.WriteInt32BE(lof1);
            cmdStream.WriteInt32BE(lof2);
            cmdStream.WriteInt32BE(lofsw);
            TcpStream.Write(cmdBuffer, 0, 24);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    pRFLevel = NetworkStreamExtensions.ReadDoubleBE(TcpStream);
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    pRFLevel = NetworkStreamExtensions.ReadDoubleBE(TcpStream);
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool FFTInit()
        {
            TcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.REQUEST_FFT_INIT);

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)(TcpStream.ReadUInt32BE());
            switch(result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool FFTScan(int freq, int pol, int lof1, int lof2, int lofsw, uint range, byte mode, byte nb_acc, uint[] pTab, ref uint pBegin, ref uint pNum)
        {
            TcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.REQUEST_FFT_SCAN);
            TcpStream.WriteInt32BE(freq);
            TcpStream.WriteInt32BE(pol);
            TcpStream.WriteInt32BE(lof1);
            TcpStream.WriteInt32BE(lof2);
            TcpStream.WriteInt32BE(lofsw);
            TcpStream.WriteUInt32BE(range);
            TcpStream.WriteUInt8(mode);
            TcpStream.WriteUInt8(nb_acc);
            TcpStream.WriteInt32BE(pTab.Length);

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)(TcpStream.ReadUInt32BE());
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    pBegin = TcpStream.ReadUInt32BE();
                    pNum = TcpStream.ReadUInt32BE();
                    for (uint i = pBegin; i < pNum; i++)
                    {
                        pTab[i] = TcpStream.ReadUInt32BE();
                    }

                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException();
            }
        }

        public bool BLScan(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr,
            ref SearchResult pSearchResult)
        {
            throw new NotImplementedException();
        }

        public bool BLScanEx(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, STD_TYPE std, ref SearchResult pSearchResult)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN_EX);
            TcpStream.WriteInt32BE(freq);
            TcpStream.WriteInt32BE(freq_range);
            TcpStream.WriteInt32BE(pol);
            TcpStream.WriteInt32BE(lof1);
            TcpStream.WriteInt32BE(lof2);
            TcpStream.WriteInt32BE(lofsw);
            TcpStream.WriteInt32BE(minsr);
            TcpStream.WriteInt32BE((int)std);
            uint opcode = TcpStream.ReadUInt32BE();
            switch (opcode)
            {
                case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    int sizeOf = Marshal.SizeOf(typeof(SearchResult));
                    byte[] enumerateBuffer = new byte[sizeOf];
                    IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);
                    TcpStream.Read(enumerateBuffer, 0, sizeOf);
                    Marshal.Copy(enumerateBuffer, 0, allocHGlobal, sizeOf);
                    pSearchResult = (SearchResult)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult));
                    Marshal.FreeHGlobal(allocHGlobal);
                    return true;
                case (uint)RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(((RemoteStreamReaderConstants)opcode).ToString());
            }
        }

        public bool BLScan2(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofsw, IntPtr pSearchResult, ref int pTpNum, BlScanCallback lpFunc)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_BL_SCAN2);
            TcpStream.WriteInt32BE(freq_start);
            TcpStream.WriteInt32BE(freq_stop);
            TcpStream.WriteInt32BE(pol);
            TcpStream.WriteInt32BE(lof1);
            TcpStream.WriteInt32BE(lof2);
            TcpStream.WriteInt32BE(lofsw);
            TcpStream.Flush();

            int sizeOf = Marshal.SizeOf(typeof(SearchResult));
            byte[] enumerateBuffer = new byte[sizeOf];
            IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);

            while (true)
            {
                uint opcode = TcpStream.ReadUInt32BE();
                switch (opcode)
                {
                    case (uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME:
                        break;
                    case (uint)RemoteStreamReaderConstants.ENUMERATE_BL_SCAN_2:
                        TcpStream.Read(enumerateBuffer, 0, sizeOf);
                        Marshal.Copy(enumerateBuffer, 0, allocHGlobal, sizeOf);
                        SearchResult sr = (SearchResult)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult));
                        lpFunc(ref sr);
                        break;
                    case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                        pTpNum = TcpStream.ReadInt32BE();
                        Marshal.FreeHGlobal(allocHGlobal);
                        return true;
                    case (uint)RemoteStreamReaderConstants.COMMAND_FAILED:
                        Marshal.FreeHGlobal(allocHGlobal);
                        return false;
                    default:
                        throw new NotImplementedException(((RemoteStreamReaderConstants)opcode).ToString());
                }
            }
        }

        public bool SignalInfo(ref SearchResult pSearchResult)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    int sizeOf = Marshal.SizeOf(typeof(SearchResult));
                    byte[] readBytes = TcpStream.ReadBytes(sizeOf);

                    IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);
                    Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf);
                    pSearchResult = (SearchResult)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult));

                    //are we sane?
                    Array.Fill(readBytes, (byte)0);
                    Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf);

                    Marshal.FreeHGlobal(allocHGlobal);
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }

            throw new NotImplementedException();
        }

        public bool IQScan(uint input, sbyte[] pIQ, uint num)
        {
	        byte[] cmdBuffer = new byte[12];
	        MemoryStream ms = new MemoryStream(cmdBuffer, 0, 12, true);
            ms.WriteUInt32BE((uint)RemoteStreamReaderConstants.REQUEST_IQ_SCAN);
            ms.WriteUInt32BE(input);
            ms.WriteUInt32BE(num);
            TcpStream.Write(cmdBuffer, 0, 12);

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    byte[] tmp = TcpStream.ReadByteArray();
                    for (long i = 0; i < num * 2; i++)
                    {
                        pIQ[i] = (sbyte)tmp[i];
                    }
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool IQScan2(uint point, short[] pIQ, uint num)
        {
            throw new NotImplementedException();
        }

        public bool IQScan2Range(byte input, ref ushort pMinPoint, ref ushort pMaxPoint)
        {
            throw new NotImplementedException();
        }

        public bool CIRScanRange(bool bHiRes, ref ushort pMinCnum, ref ushort pMaxCnum, ref int pMinDelayNs, ref int pMaxDelayNs)
        {
            throw new NotImplementedException();
        }

        public bool CIRScan(bool bHiRes, int[] pPowers, int[] pDelays)
        {
            throw new NotImplementedException();
        }

        public bool CarRange(ref ushort pMinCnum, ref ushort pMaxCnum)
        {
            throw new NotImplementedException();
        }

        public bool CarEsNo(ref ushort cnum, ref double pEsNo)
        {
            throw new NotImplementedException();
        }

        public bool MISSel(bool bEnable, byte misFilter, byte misFilterMask)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_MIS_SEL);
            TcpStream.WriteBoolean(bEnable);
            NetworkStreamExtensions.WriteUInt8(TcpStream, misFilter);
            NetworkStreamExtensions.WriteUInt8(TcpStream, misFilterMask);
            TcpStream.Flush();
            uint result = TcpStream.ReadUInt32BE();
            switch (result)
            {
                case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case (uint)RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(((RemoteStreamReaderConstants)result).ToString());
            }
        }

        public bool PLSSel(byte plsMode, uint code)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_PLS_SEL);
            NetworkStreamExtensions.WriteUInt8(TcpStream, plsMode);
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, code);
            TcpStream.Flush();
            uint result = TcpStream.ReadUInt32BE();
            switch (result)
            {
                case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case (uint)RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(((RemoteStreamReaderConstants)result).ToString());
            }
        }

        public bool PLSGet(byte pMode, ref uint pCode)
        {
            throw new NotImplementedException();
        }

        public bool ModSel(ref S2Mode ps2Modes, uint num)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_MOD_SEL);
            TcpStream.WriteInt32BE(ps2Modes.frameLen);
            TcpStream.WriteInt32BE((int)ps2Modes.modcode);
            TcpStream.WriteBoolean(ps2Modes.pilot);
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, num);
            TcpStream.Flush();
            uint result = TcpStream.ReadUInt32BE();
            switch (result)
            {
                case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    ps2Modes.frameLen = TcpStream.ReadInt32BE();
                    ps2Modes.modcode = (S2MODCODE)TcpStream.ReadInt32BE();
                    ps2Modes.pilot = TcpStream.ReadBoolean();
                    return true;
                case (uint)RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(((RemoteStreamReaderConstants)result).ToString());
            }
        }

        public bool ModInv(uint WaitMs, ref S2Mode pS2Modes, ref uint pNum)
        {
            throw new NotImplementedException();
        }

        public bool SetChannel2(uint freq, uint bandwidth)
        {
            if (!TcpStream.Socket.Connected)
            {
                Reconnect();
            }
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SET_CHANNEL_2);
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, freq);
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, bandwidth);
            TcpStream.Flush();

            RemoteStreamReaderConstants response = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (response)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(response.ToString());
            }
        }

        public bool SetChannel2Ex(uint freq, uint bandwidth, STD_TYPE std, int stream)
        {
            throw new NotImplementedException();
        }

        public bool SetChannel2ExEx(uint freq, uint bandwidth, uint symbrate, STD_TYPE std, int stream)
        {
            throw new NotImplementedException();
        }

        public bool SignalInfo2(ref SearchResult2 si2)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SIGNAL_INFO_2);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch (result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    int sizeOf = Marshal.SizeOf(typeof(SearchResult2));
                    byte[] readBytes = TcpStream.ReadBytes(sizeOf);

                    IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);
                    Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf);
                    si2 = (SearchResult2)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult2));

                    //are we sane?
                    Array.Fill(readBytes, (byte)0);
                    Marshal.Copy(readBytes, 0, allocHGlobal, sizeOf);

                    Marshal.FreeHGlobal(allocHGlobal);
                    return true;
                default:
                    throw new NotImplementedException(result.ToString());
            }

            throw new NotImplementedException();
        }

        public bool RFScan2(uint freq, STD_TYPE std, ref double pRFLevel)
        {
            TcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.REQUEST_RF_SCAN_2);
            TcpStream.WriteUInt32BE(freq);
            TcpStream.WriteInt32BE((int)std);
            TcpStream.Flush();

            RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
            switch(result)
            {
                case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    pRFLevel = TcpStream.ReadDoubleBE();
                    return true;
                case RemoteStreamReaderConstants.COMMAND_FAILED:
                    return false;
                default:
                    throw new NotImplementedException(result.ToString());
            }
        }

        public bool AirScan(int freq_start, int freq_stop, uint step, uint bandwidth, int std, IntPtr pSearchResult, ref int pTpNum, AirScanCallback lpFunc)
        {
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_AIR_SCAN);
            TcpStream.WriteInt32BE(freq_start);
            TcpStream.WriteInt32BE(freq_stop);
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, step);
            NetworkStreamExtensions.WriteUInt32BE(TcpStream, bandwidth);
            TcpStream.WriteInt32BE(std);
            TcpStream.Flush();

            int sizeOf = Marshal.SizeOf(typeof(SearchResult2));
            byte[] enumerateBuffer = new byte[sizeOf];
            IntPtr allocHGlobal = Marshal.AllocHGlobal(sizeOf);

            while (true)
            {
                uint opcode = TcpStream.ReadUInt32BE();
                switch (opcode)
                {
                    case (uint)RemoteStreamReaderConstants.COMMAND_WILL_TAKE_SOME_TIME:
                        break;
                    case (uint)RemoteStreamReaderConstants.ENUMERATE_AIR_SCAN:
                        TcpStream.Read(enumerateBuffer, 0, sizeOf);
                        Marshal.Copy(enumerateBuffer, 0, allocHGlobal, sizeOf);
                        SearchResult2 sr = (SearchResult2)Marshal.PtrToStructure(allocHGlobal, typeof(SearchResult2));
                        lpFunc(ref sr);
                        break;
                    case (uint)RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                        pTpNum = TcpStream.ReadInt32BE();
                        Marshal.FreeHGlobal(allocHGlobal);
                        return true;
                    case (uint)RemoteStreamReaderConstants.COMMAND_FAILED:
                        Marshal.FreeHGlobal(allocHGlobal);
                        return false;
                }
            }
        }

        public bool GetEEPROM(byte[] buffer, int offset, int len)
        {
            throw new NotImplementedException();
        }

        public bool SetEEPROM(byte[] buffer, int offset, int len)
        {
            throw new NotImplementedException();
        }

		public Version GetEngineVersion()
		{
			NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_VERSION);
			TcpStream.Flush();

			RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
			switch (result)
			{
				case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    Version payload = TcpStream.ReadVersion();
                    return payload;
				default:
					throw new NotImplementedException(result.ToString());
			}
		}

		public string GetEngineName()
		{
			NetworkStreamExtensions.WriteUInt32BE(TcpStream, (uint)RemoteStreamReaderConstants.REQUEST_SKYSCRAPER_ENGINE_NAME);
			TcpStream.Flush();

			RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
			switch (result)
			{
				case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
                    string payload = TcpStream.ReadUTF8();
					return payload;
				default:
					throw new NotImplementedException(result.ToString());
			}
		}

		public bool FFTTerm()
		{
            TcpStream.WriteUInt32BE((uint)RemoteStreamReaderConstants.REQUEST_FFT_TERM);
            TcpStream.Flush();

			RemoteStreamReaderConstants result = (RemoteStreamReaderConstants)TcpStream.ReadUInt32BE();
			switch (result)
			{
				case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
					return true;
				case RemoteStreamReaderConstants.COMMAND_FAILED:
					return false;
				default:
					throw new NotImplementedException(result.ToString());
			}
		}
	}
}
