using System;
using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable FieldCanBeMadeReadOnly.Local

namespace Avalonia.LinuxFramebuffer.Output
{
    public enum DrmModeConnection
    {
        DRM_MODE_CONNECTED = 1,
        DRM_MODE_DISCONNECTED = 2,
        DRM_MODE_UNKNOWNCONNECTION = 3
    }
        
    public enum DrmModeSubPixel{
        DRM_MODE_SUBPIXEL_UNKNOWN        = 1,
        DRM_MODE_SUBPIXEL_HORIZONTAL_RGB = 2,
        DRM_MODE_SUBPIXEL_HORIZONTAL_BGR = 3,
        DRM_MODE_SUBPIXEL_VERTICAL_RGB   = 4,
        DRM_MODE_SUBPIXEL_VERTICAL_BGR   = 5,
        DRM_MODE_SUBPIXEL_NONE           = 6
    }
    
    static unsafe class LibDrm
    {
        private const string libdrm = "libdrm.so.2";
        private const string libgbm = "libgbm.so.1";
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void DrmEventVBlankHandlerDelegate(int fd,
            uint sequence,
            uint tv_sec,
            uint tv_usec,
            void* user_data);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void DrmEventPageFlipHandlerDelegate(int fd,
            uint sequence,
            uint tv_sec,
            uint tv_usec,
            void* user_data);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate IntPtr DrmEventPageFlipHandler2Delegate(int fd,
            uint sequence,
            uint tv_sec,
            uint tv_usec,
            uint crtc_id,
            void* user_data);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void DrmEventSequenceHandlerDelegate(int fd,
            ulong sequence,
            ulong ns,
            ulong user_data);

        [StructLayout(LayoutKind.Sequential)]
        public struct DrmEventContext
        {
            public int version; //4
            public IntPtr vblank_handler;
            public IntPtr page_flip_handler;
            public IntPtr page_flip_handler2;
            public IntPtr sequence_handler;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        public struct drmModeRes {

            public int count_fbs;
            public uint *fbs;

            public int count_crtcs;
            public uint *crtcs;

            public int count_connectors;
            public uint *connectors;

            public int count_encoders;
            public uint *encoders;

            uint min_width, max_width;
            uint min_height, max_height;
        }

        [Flags]
        public enum DrmModeType
        {
            DRM_MODE_TYPE_BUILTIN = (1 << 0),
            DRM_MODE_TYPE_CLOCK_C = ((1 << 1) | DRM_MODE_TYPE_BUILTIN),
            DRM_MODE_TYPE_CRTC_C = ((1 << 2) | DRM_MODE_TYPE_BUILTIN),
            DRM_MODE_TYPE_PREFERRED = (1 << 3),
            DRM_MODE_TYPE_DEFAULT = (1 << 4),
            DRM_MODE_TYPE_USERDEF = (1 << 5),
            DRM_MODE_TYPE_DRIVER = (1 << 6)
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct drmModeModeInfo
        {
            public uint clock;
            public ushort hdisplay, hsync_start, hsync_end, htotal, hskew;
            public ushort vdisplay, vsync_start, vsync_end, vtotal, vscan;

            public uint vrefresh;

            public uint flags;
            public DrmModeType type;
            public fixed byte name[32];
            public PixelSize Resolution => new PixelSize(hdisplay, vdisplay);
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct drmModeConnector {
            public uint connector_id;
            public uint encoder_id; // Encoder currently connected to 
            public uint connector_type;
            public uint connector_type_id;
            public DrmModeConnection connection;
            public uint mmWidth, mmHeight; //  HxW in millimeters 
            public DrmModeSubPixel subpixel;

            public int count_modes;
            public drmModeModeInfo* modes;

            public int count_props;
            public uint *props; // List of property ids 
            public ulong *prop_values; // List of property values 

            public int count_encoders;
            public uint *encoders; //List of encoder ids
        }
        
        [StructLayout(LayoutKind.Sequential)]
        public struct drmModeEncoder {
            public uint encoder_id;
            public uint encoder_type;
            public uint crtc_id;
            public uint possible_crtcs;
            public uint possible_clones;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        public struct drmModeCrtc {
            public uint crtc_id;
            public uint buffer_id; // FB id to connect to 0 = disconnect 

            public uint x, y; // Position on the framebuffer 
            public uint width, height;
            public int mode_valid;
            public drmModeModeInfo mode;

            public int gamma_size; // Number of gamma stops 

        }
        
        [DllImport(libdrm, SetLastError = true)]
        public static extern drmModeRes* drmModeGetResources(int fd);
        [DllImport(libdrm, SetLastError = true)]
        public static extern void drmModeFreeResources(drmModeRes* res);

        [DllImport(libdrm, SetLastError = true)]
        public static extern drmModeConnector* drmModeGetConnector(int fd, uint connector);
        [DllImport(libdrm, SetLastError = true)]
        public static extern void drmModeFreeConnector(drmModeConnector* res);
        
        [DllImport(libdrm, SetLastError = true)]
        public static extern drmModeEncoder* drmModeGetEncoder(int fd, uint id);
        [DllImport(libdrm, SetLastError = true)]
        public static extern void drmModeFreeEncoder(drmModeEncoder* enc);
        [DllImport(libdrm, SetLastError = true)]
        public static extern drmModeCrtc* drmModeGetCrtc(int fd, uint id);
        [DllImport(libdrm, SetLastError = true)]
        public static extern void drmModeFreeCrtc(drmModeCrtc* enc);

        [DllImport(libdrm, SetLastError = true)]
        public static extern int drmModeAddFB(int fd, uint width, uint height, byte depth,
            byte bpp, uint pitch, uint bo_handle,
            out uint buf_id);

        [DllImport(libdrm, SetLastError = true)]
        public static extern int drmModeAddFB2(int fd, uint width, uint height,
            uint pixel_format, uint[] bo_handles, uint[] pitches,
            uint[] offsets, out uint buf_id, uint flags);

        [DllImport(libdrm, SetLastError = true)]
        public static extern int drmModeSetCrtc(int fd, uint crtcId, uint bufferId,
            uint x, uint y, uint *connectors, int count,
            drmModeModeInfo* mode);
        
        [DllImport(libdrm, SetLastError = true)]
        public static extern void drmModeRmFB(int fd, int id);

        [Flags]
        public enum DrmModePageFlip
        {
            Event = 1,
            Async = 2,
            Absolute = 4,
            Relative = 8,
        }

        [DllImport(libdrm, SetLastError = true)]
        public static extern void drmModePageFlip(int fd, uint crtc_id, uint fb_id,
            DrmModePageFlip flags, void *user_data);


        [DllImport(libdrm, SetLastError = true)]
        public static extern void drmHandleEvent(int fd, DrmEventContext* context);

        [DllImport(libgbm, SetLastError = true)]
        public static extern IntPtr gbm_create_device(int fd);
        
        
        [Flags]
        public enum GbmBoFlags {
            /**
             * Buffer is going to be presented to the screen using an API such as KMS
             */
            GBM_BO_USE_SCANOUT      = (1 << 0),
            /**
             * Buffer is going to be used as cursor
             */
            GBM_BO_USE_CURSOR       = (1 << 1),
            /**
             * Deprecated
             */
            GBM_BO_USE_CURSOR_64X64 = GBM_BO_USE_CURSOR,
            /**
             * Buffer is to be used for rendering - for example it is going to be used
             * as the storage for a color buffer
             */
            GBM_BO_USE_RENDERING    = (1 << 2),
            /**
             * Buffer can be used for gbm_bo_write.  This is guaranteed to work
             * with GBM_BO_USE_CURSOR, but may not work for other combinations.
             */
            GBM_BO_USE_WRITE    = (1 << 3),
            /**
             * Buffer is linear, i.e. not tiled.
             */
            GBM_BO_USE_LINEAR = (1 << 4),
        };

        [DllImport(libgbm, SetLastError = true)]
        public static extern IntPtr gbm_surface_create(IntPtr device, int width, int height, uint format, GbmBoFlags flags);
        [DllImport(libgbm, SetLastError = true)]
        public static extern IntPtr gbm_surface_lock_front_buffer(IntPtr surface);
        [DllImport(libgbm, SetLastError = true)]
        public static extern int gbm_surface_release_buffer(IntPtr surface, IntPtr bo);
        [DllImport(libgbm, SetLastError = true)]
        public static extern IntPtr gbm_bo_get_user_data(IntPtr surface);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void GbmBoUserDataDestroyCallbackDelegate(IntPtr bo, IntPtr data);

        [DllImport(libgbm, SetLastError = true)]
        public static extern IntPtr gbm_bo_set_user_data(IntPtr bo, IntPtr userData,
            GbmBoUserDataDestroyCallbackDelegate onFree);

        [DllImport(libgbm, SetLastError = true)]
        public static extern uint gbm_bo_get_width(IntPtr bo);

        [DllImport(libgbm, SetLastError = true)]
        public static extern uint gbm_bo_get_height(IntPtr bo);

        [DllImport(libgbm, SetLastError = true)]
        public static extern uint gbm_bo_get_stride(IntPtr bo);

        [DllImport(libgbm, SetLastError = true)]
        public static extern uint gbm_bo_get_format(IntPtr bo);

        [StructLayout(LayoutKind.Explicit)]
        public struct GbmBoHandle
        {
            [FieldOffset(0)]
            public void *ptr;
            [FieldOffset(0)]
            public int s32;
            [FieldOffset(0)]
            public uint u32;
            [FieldOffset(0)]
            public long s64;
            [FieldOffset(0)]
            public ulong u64;
        }
        
        [DllImport(libgbm, SetLastError = true)]
        public static extern GbmBoHandle gbm_bo_get_handle(IntPtr bo);

        public static  class GbmColorFormats
        {
            public static uint FourCC(char a, char b, char c, char d) =>
                (uint)a | ((uint)b) << 8 | ((uint)c) << 16 | ((uint)d) << 24;

            public static uint GBM_FORMAT_XRGB8888 { get; } = FourCC('X', 'R', '2', '4');
        }
    }
    
}
