using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using AOT;

namespace UnityGoDiceInterface {
    public class NativeDiceInterfaceImports {
        public delegate void DeviceFoundDelegateMessage(string identifier, string name);
        public delegate void DataDelegateMessage(string identifier, List<byte> bytes);
        public delegate void DeviceConnectedDelegateMessage(string identifier);
        public delegate void DeviceConnectionFailedDelegateMessage(string identifier);
        public delegate void DeviceDisconnectedDelegateMessage(string identifier);
        public delegate void ListenerStoppedDelegateMessage();
        public delegate void LoggerDelegateMessage(string log);

        private static DeviceFoundDelegateMessage deviceFoundDelegate;
        private static DataDelegateMessage dataDelegate;
        private static DeviceConnectedDelegateMessage deviceConnectedDelegate;
        private static DeviceConnectionFailedDelegateMessage deviceConnectionFailedDelegate;
        private static DeviceDisconnectedDelegateMessage deviceDisconnectedDelegate;
        private static ListenerStoppedDelegateMessage listenerStoppedDelegate;
        private static LoggerDelegateMessage loggerDelegateMessage;

#if UNITY_IOS
        private const string BundleName = "__Internal";
#elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
        private const string BundleName = "DarwinGodiceBundle";
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
        private const string BundleName = "GoDiceDll.dll";
#endif

        private delegate void MonoDeviceFoundDelegateMessage(string identifier, string name);
        private delegate void
        MonoDataDelegateMessage(string identifier, UInt32 byteCount, IntPtr bytePtr);
        private delegate void MonoDeviceConnectedDelegateMessage(string identifier);
        private delegate void MonoDeviceConnectionFailedDelegateMessage(string identifier);
        private delegate void MonoDeviceDisconnectedDelegateMessage(string identifier);
        private delegate void MonoListenerStoppedDelegateMessage();
        private delegate void MonoLoggerDelegateMessage(string log);

        [DllImport(dllName: BundleName, EntryPoint = "godice_start_listening")]
        private static extern void _NativeBridgeStartListening();

        [DllImport(dllName: BundleName, EntryPoint = "godice_stop_listening")]
        private static extern void _NativeBridgeStopListening();

        [DllImport(dllName: BundleName, EntryPoint = "godice_set_callbacks")]
        private static extern void _NativeBridgeSetCallbacks(
                MonoDeviceFoundDelegateMessage deviceFoundDelegate,
                MonoDataDelegateMessage dataDelegate,
                MonoDeviceConnectedDelegateMessage deviceConnectedDelegate,
                MonoDeviceConnectionFailedDelegateMessage deviceConnectionFailedDelegateMessage,
                MonoDeviceDisconnectedDelegateMessage deviceDisconnectedDelegate,
                MonoListenerStoppedDelegateMessage listenerStoppedDelegate);

        [DllImport(dllName: BundleName, EntryPoint = "godice_connect")]
        private static extern void _NativeBridgeConnect(string identifier);

        [DllImport(dllName: BundleName, EntryPoint = "godice_disconnect")]
        private static extern void _NativeBridgeDisconnect(string identifier);

        [DllImport(dllName: BundleName, EntryPoint = "godice_send")]
        private static extern void
        _NativeBridgeSend(string identifier, UInt32 byteCount, IntPtr bytePtr);

        [DllImport(dllName: BundleName, EntryPoint = "godice_set_logger")]
        private static extern void _NativeBridgeSetLogger(MonoLoggerDelegateMessage loggerDelegate);

        [DllImport(dllName: BundleName, EntryPoint = "godice_reset")]
        private static extern void _NativeBridgeReset();

        private static List<byte> BytesFromRawPointer(UInt32 byteCount, IntPtr bytes) {
            byte[] array = new byte[byteCount];
            if (byteCount > 0) { Marshal.Copy(bytes, array, 0, (int)byteCount); }
            return new List<byte>(array);
        }

        /*
         * Callback methods
         */
        [MonoPInvokeCallback(typeof(MonoDeviceFoundDelegateMessage))]
        private static void MonoDeviceFoundMessageReceived(string identifier, string name) {
            if (deviceFoundDelegate != null) { deviceFoundDelegate(identifier, name); }
        }

        [MonoPInvokeCallback(typeof(MonoDataDelegateMessage))]
        private static void
        MonoDataMessageReceived(string identifier, UInt32 byteCount, IntPtr bytePtr) {
            if (dataDelegate != null) {
                dataDelegate(identifier, BytesFromRawPointer(byteCount, bytePtr));
            }
        }

        [MonoPInvokeCallback(typeof(MonoDeviceConnectedDelegateMessage))]
        private static void MonoDeviceConnected(string identifier) {
            if (deviceConnectedDelegate != null) { deviceConnectedDelegate(identifier); }
        }

        [MonoPInvokeCallback(typeof(MonoDeviceConnectionFailedDelegateMessage))]
        private static void MonoDeviceConnectionFailed(string identifier) {
            if (deviceConnectionFailedDelegate != null) {
                deviceConnectionFailedDelegate(identifier);
            }
        }

        [MonoPInvokeCallback(typeof(MonoDeviceDisconnectedDelegateMessage))]
        private static void MonoDeviceDisconnected(string identifier) {
            if (deviceDisconnectedDelegate != null) { deviceDisconnectedDelegate(identifier); }
        }

        [MonoPInvokeCallback(typeof(MonoListenerStoppedDelegateMessage))]
        private static void MonoListenerStopped() {
            if (listenerStoppedDelegate != null) { listenerStoppedDelegate(); }
        }

        [MonoPInvokeCallback(typeof(MonoLoggerDelegateMessage))]
        private static void MonoLogger(string log) {
            if (loggerDelegateMessage != null) { loggerDelegateMessage(log); }
        }

        public void StartListening() {
            _NativeBridgeSetCallbacks(
                    MonoDeviceFoundMessageReceived,
                    MonoDataMessageReceived,
                    MonoDeviceConnected,
                    MonoDeviceConnectionFailed,
                    MonoDeviceDisconnected,
                    MonoListenerStopped);
            _NativeBridgeSetLogger(MonoLogger);
            _NativeBridgeStartListening();
        }

        public void StopListening() { _NativeBridgeStopListening(); }

        public void Connect(string identifier) { _NativeBridgeConnect(identifier); }

        public void Disconnect(string identifier) { _NativeBridgeDisconnect(identifier); }

        public void Send(string identifier, List<byte> data) {
            byte[] byteArray = data.ToArray();
            GCHandle pinnedArray = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
            IntPtr pointer = pinnedArray.AddrOfPinnedObject();
            _NativeBridgeSend(identifier, (uint)data.Count, pointer);
            pinnedArray.Free();
        }

        public void SetDeviceFoundCallback(DeviceFoundDelegateMessage deviceFound) {
            deviceFoundDelegate = deviceFound;
        }

        public void SetDataCallback(DataDelegateMessage data) { dataDelegate = data; }

        public void SetDeviceConnectedCallback(DeviceConnectedDelegateMessage deviceConnected) {
            deviceConnectedDelegate = deviceConnected;
        }

        public void SetDeviceConnectionFailedCallback(
                DeviceConnectionFailedDelegateMessage deviceConnectionFailed) {
            deviceConnectionFailedDelegate = deviceConnectionFailed;
        }

        public void SetDeviceDisconnectedCallback(
                DeviceDisconnectedDelegateMessage deviceDisconnected) {
            deviceDisconnectedDelegate = deviceDisconnected;
        }

        public void SetListenerStoppedCallback(ListenerStoppedDelegateMessage listenerStopped) {
            listenerStoppedDelegate = listenerStopped;
        }

        public void SetLoggerCallback(LoggerDelegateMessage logger) {
            loggerDelegateMessage = logger;
        }

        public void Reset() { _NativeBridgeReset(); }
    }
}
