using System;
using System.Runtime.InteropServices;

namespace skyscraper5.Skyscraper.IO.LibDvbV5
{
    public class LibDvbV5Device : IDisposable
    {
        public LibDvbV5Device()
        {
            dvbDevAlloc = LibDvbV5.DvbDevAlloc();
        }

        private IntPtr dvbDevAlloc;

        public void Find()
        {
            if (LibDvbV5.DvbDevFind(dvbDevAlloc, 0) != 0)
            {
                throw new Exception("dvb_dev_find failed");
            }

            DvbDeviceStruct unpacked = Marshal.PtrToStructure<DvbDeviceStruct>(dvbDevAlloc);
            LibDvbV5DeviceList[] devices = new LibDvbV5DeviceList[unpacked.numDevices];
            IntPtr currentDevicePtr = unpacked.devices;
            for (int i = 0; i < unpacked.numDevices; i++)
            {
                devices[i] = new LibDvbV5DeviceList(currentDevicePtr);
                currentDevicePtr = new IntPtr(currentDevicePtr.ToInt64() + devices[i].SizeOf);
            }
            KnownDevices = devices;
        }
        public LibDvbV5DeviceList[] KnownDevices { get; private set; }
        
        public void Dispose()
        {
            LibDvbV5.DvbDevFree(dvbDevAlloc);
            dvbDevAlloc = IntPtr.Zero;
        }

        public LibDvbV5DeviceList SeekBySystemName(uint adapter, uint num, DvbDeviceType type)
        {
            IntPtr result = LibDvbV5.DvbDevSeekBySysname(dvbDevAlloc, adapter, num, type);
            if (result== IntPtr.Zero)
            {
                throw new Exception("desired device was not found");
            }

            return new LibDvbV5DeviceList(result);
        }
        
        private struct DvbDeviceStruct
        {
            public IntPtr devices;
            public int numDevices;
            public IntPtr feParams;
        }

        public LibDvbV5OpenDescriptor DeviceOpen(LibDvbV5DeviceList sysname)
        {
            int permissions = LibDvbV5.O_RDWR;
            if (sysname.DvbType == DvbDeviceType.Dvr)
                permissions = LibDvbV5.O_RDONLY;

            IntPtr dvbDevOpen = LibDvbV5.DvbDevOpen(this.dvbDevAlloc, sysname.SystemName, permissions);
            if (dvbDevOpen == IntPtr.Zero)
                return null;
            return new LibDvbV5OpenDescriptor(dvbDevOpen);
        }

        public void SetLog(uint verbosityLevel, LibDvbV5LogFunction logFunction)
        {
            LibDvbV5.DvbDevSetLog(this.dvbDevAlloc, verbosityLevel, logFunction);
        }

        public LibDvbV5DeviceList FindDeviceByDeliverySystem(LibDvbV5FrontendDeliverySystem deliverySystem)
        {
            if (KnownDevices == null)
                Find();

            foreach (LibDvbV5DeviceList deviceList in KnownDevices)
            {
                if (deviceList.DvbType != DvbDeviceType.Frontend)
                    continue;

                uint? adapterNo = deviceList.GuessAdapterNo();
                uint? deviceNo = deviceList.GuessDeviceNo();

                if (!adapterNo.HasValue || !deviceNo.HasValue)
                    continue;

                LibDvb5FrontendParameters frontend = new LibDvb5FrontendParameters(adapterNo.Value, deviceNo.Value);
                LibDvbV5FrontendDeliverySystem[] deliverySystems = frontend.GetDeliverySystems();
                frontend.Dispose();
                foreach (LibDvbV5FrontendDeliverySystem libDvbV5FrontendDeliverySystem in deliverySystems)
                {
                    if (libDvbV5FrontendDeliverySystem == deliverySystem)
                    {
                        return deviceList;
                    }
                }
            }

            return null;
        }

        public LibDvbV5DeviceList GetSisterDevice(LibDvbV5DeviceList findDeviceByDeliverySystem, DvbDeviceType demux)
        {
            uint? guessAdapterNo = findDeviceByDeliverySystem.GuessAdapterNo();
            if (guessAdapterNo == null)
                return null;

            return SeekBySystemName(guessAdapterNo.Value, 0, demux);
        }
    }
}