#if UNITY_IOS || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
#define USE_SWIFT_INTERFACE
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || ENABLE_WINMD_SUPPORT
#define USE_WINDOWS_INTERFACE
#else
#endif

using System.Collections.Generic;
using UnityEngine;
using UnityGoDiceInterface;

public class DiceInterface : MonoBehaviour {
    public struct Commands {
        public static readonly byte PulseLed = 16;
        public static readonly byte GetColor = 23;
    }

    readonly NativeDiceInterfaceImports _diceInterfaceImports = new();
    private static DiceInterface _singleton = null;

    private static Dictionary<string, string> _deviceNames = new Dictionary<string, string>();

    public delegate void DeviceFoundCallback(string identifier);
    public delegate void ConnectionCallback(string identifier, string deviceName);
    public delegate void ConnectionFailedCallback(string identifier);
    public delegate void DisconnectionCallback(string identifier);
    public delegate void ListenerStoppedCallback();
    public delegate void RollCallback(string identifier, sbyte x, sbyte y, sbyte z);
    public delegate void ColorCallback(string identifier, DieColor color);
    public delegate void LoggerCallback(string log);

    public DeviceFoundCallback deviceFoundCallback = null;
    public ConnectionCallback connectionCallback = null;
    public ConnectionFailedCallback connectionFailedCallback = null;
    public DisconnectionCallback disconnectionCallback = null;
    public ListenerStoppedCallback listenerStoppedCallback = null;
    public RollCallback rollCallback = null;
    public ColorCallback colorCallback = null;
    public LoggerCallback logger = null;

    public void StartListening() { _diceInterfaceImports.StartListening(); }

    public void StopListening() { _diceInterfaceImports.StopListening(); }

    public void Connect(string identifier) { _diceInterfaceImports.Connect(identifier); }
    public void Disconnect(string identifier) { _diceInterfaceImports.Disconnect(identifier); }

    public void Reset() {
        _diceInterfaceImports.Reset();
        _deviceNames.Clear();
    }

    public void RequestColor(string identifier) {
        _diceInterfaceImports.Send(identifier, new List<byte> { Commands.GetColor });
    }

    public void FlashColor(
            string identifier,
            byte pulseCount,
            byte onTime10ms,
            byte offTime10ms,
            byte red,
            byte green,
            byte blue) {
        _diceInterfaceImports.Send(
                identifier,
                new List<byte> {
                    Commands.PulseLed,
                    pulseCount,
                    onTime10ms,
                    offTime10ms,
                    red,
                    green,
                    blue,
                    0x01,
                    0x00
                });
    }

    // Start is called before the first frame update
    void Start() {
        _singleton = this;

        _diceInterfaceImports.SetDeviceConnectedCallback(DeviceConnectedDelegateMessageReceived);
        _diceInterfaceImports.SetDeviceConnectionFailedCallback(
                DeviceConnectionFailedDelegateMessageReceived);
        _diceInterfaceImports.SetDeviceDisconnectedCallback(
                DeviceDisconnectedDelegateMessageReceived);
        _diceInterfaceImports.SetDeviceFoundCallback(DeviceFoundDelegateMessageReceived);
        _diceInterfaceImports.SetDataCallback(DataDelegateMessageReceived);
        _diceInterfaceImports.SetListenerStoppedCallback(ListenerStoppedDelegateMessageReceived);
        _diceInterfaceImports.SetLoggerCallback(LoggerDelegateMessageReceived);
    }

    // Update is called once per frame
    void Update() {}

    private static sbyte[] GetRollVector(List<byte> rawData) {
        if (rawData.Count < 1) {
            Debug.Log("rollVector: no data");
            return null;
        }
        byte firstByte = rawData[0];
        if (firstByte != 83) {
            Debug.Log("rollVector: first byte is not 83");
            return null;
        }
        if (rawData.Count != 4) {
            Debug.Log("rollVector: data length is not 4");
            return null;
        }
        return new[] { (sbyte)rawData[1], (sbyte)rawData[2], (sbyte)rawData[3] };
    }

    private static void DeviceFoundDelegateMessageReceived(string identifier, string deviceName) {
        if (_singleton != null) {
            _deviceNames[identifier] = deviceName;
            if (_singleton.deviceFoundCallback != null) {
                _singleton.deviceFoundCallback(identifier);
            }
        }
    }

    private static void DeviceConnectedDelegateMessageReceived(string identifier) {
        if (_singleton != null && _singleton.connectionCallback != null) {
            _singleton.connectionCallback(identifier, _deviceNames[identifier]);
        }
    }

    private static void DeviceConnectionFailedDelegateMessageReceived(string identifier) {
        if (_singleton != null && _singleton.connectionFailedCallback != null) {
            _singleton.connectionFailedCallback(identifier);
        }
    }

    private static void DeviceDisconnectedDelegateMessageReceived(string identifier) {
        if (_singleton != null && _singleton.disconnectionCallback != null) {
            _singleton.disconnectionCallback(identifier);
        }
    }

    private static void DataDelegateMessageReceived(string identifier, List<byte> byteList) {
        if (_singleton != null) {
            if (byteList.Count == 0) {
                if (_singleton.connectionCallback != null) {
                    // I don't think this one should still happen
                    return;
                }
            } else {
                byte firstByte = byteList[0];

                switch (firstByte) {
                    case 82:
                        // Roll started
                        break;
                    case 66:
                        // Battery level
                        break;
                    case (byte)'C':
                        if (byteList[1] == (byte)'o' && byteList[2] == (byte)'l') {
                            byte colorRawValue = byteList[3];

                            _singleton.colorCallback(identifier, (DieColor)colorRawValue);
                        }
                        // Color (fetched)
                        break;
                    case 83: {
                        var roll = GetRollVector(byteList);
                        if (roll != null && _singleton.rollCallback != null) {
                            _singleton.rollCallback(identifier, roll[0], roll[1], roll[2]);
                        }
                        break;
                    }
                    case 70:
                    case 84:
                    case 77: {
                        byteList.RemoveAt(0);
                        var roll = GetRollVector(byteList);
                        if (roll != null && _singleton.rollCallback != null) {
                            _singleton.rollCallback(identifier, roll[0], roll[1], roll[2]);
                        }
                        break;
                    }
                    default: Debug.Log("Not yet handled"); break;
                }
            }
        }
    }

    private static void ListenerStoppedDelegateMessageReceived() {
        if (_singleton != null && _singleton.listenerStoppedCallback != null) {
            _singleton.listenerStoppedCallback();
        }
    }

    private static void LoggerDelegateMessageReceived(string log) {
        if (_singleton != null && _singleton.logger != null) { _singleton.logger(log); }
    }

    public void OnDestroy() {
        _diceInterfaceImports.SetDeviceConnectedCallback(null);
        _diceInterfaceImports.SetDeviceConnectionFailedCallback(null);
        _diceInterfaceImports.SetDeviceDisconnectedCallback(null);
        _diceInterfaceImports.SetDeviceFoundCallback(null);
        _diceInterfaceImports.SetDataCallback(null);
        _diceInterfaceImports.SetListenerStoppedCallback(null);
        _diceInterfaceImports.SetLoggerCallback(null);
#if UNITY_EDITOR
        StopListening();
        Reset();
#endif
    }
}
