﻿using System;
using System.Collections.Generic;
using UnityEngine;

public class AidlabAndroid : IAidlab {

    public string firmwareRevision => device?.Get<string>("firmwareRevision") ?? "";
    public string hardwareRevision => device?.Get<string>("hardwareRevision") ?? "";
    public string serialNumber => device?.Get<string>("serialNumber") ?? "";

    public void disconnect() {
        device?.Call("disconnect");
    }

    public void startSynchronization() {
        device?.Call("startSynchronization");
    }

    public void stopSynchronization() {
        device?.Call("stopSynchronization");
    }

    public void send(byte[] payload, int processId = 0) {
        if (payload == null || payload.Length == 0 || device == null) {
            return;
        }

        device.Call("send", payload, processId);
    }

    public AidlabAndroid(AidlabDelegate aidlabDelegate) {
        this.aidlabDelegate = aidlabDelegate;

        var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

        aidlabManager = new AndroidJavaObject("com.aidlab.sdk.AidlabManager", currentActivity, new AidlabSDK(this));
        scanForAidlab();
    }

    public void onDeviceDiscovered(AndroidJavaObject discoveredDevice, int rssi) {
        if (deviceDetected) {
            return;
        }

        deviceDetected = true;
        aidlabManager?.Call("stopScan");

        deviceDelegateProxy = new DeviceDelegateProxy(this);
        discoveredDevice.Call("connect", deviceDelegateProxy);
    }

    public void onDeviceScanStartedEvent() { }

    public void onDeviceScanStoppedEvent() {
        if (!deviceDetected) {
            scanForAidlab();
        }
    }

    public void onDeviceScanFailedEvent(int errorCode) {
        aidlabDelegate?.didReceiveError($"Scan failed (code={errorCode}). Check Bluetooth permissions.");
    }

    private void onConnected(AndroidJavaObject device) {
        this.device = device;
        aidlabDelegate?.didConnectAidlab(this);

        var live = BuildCollectEnumSet();
        var store = BuildCollectEnumSet();
        device.Call("collect", live, store);
    }

    private void onDisconnected(AndroidJavaObject disconnectReason) {
        deviceDetected = false;
        device = null;

        aidlabDelegate?.didDisconnectAidlab(this, ParseDisconnectReason(disconnectReason));
    }

    private void scanForAidlab() {
        aidlabManager?.Call("scan");
    }

    private AndroidJavaObject BuildCollectEnumSet() {
        var classClass = new AndroidJavaClass("java.lang.Class");
        var dataTypeClass = classClass.CallStatic<AndroidJavaObject>("forName", "com.aidlab.sdk.DataType");
        var enumSetClass = new AndroidJavaClass("java.util.EnumSet");
        var enumSet = enumSetClass.CallStatic<AndroidJavaObject>("noneOf", dataTypeClass);

        var dataType = new AndroidJavaClass("com.aidlab.sdk.DataType");
        string[] names = {
            "ECG",
            "RESPIRATION",
            "SKIN_TEMPERATURE",
            "MOTION",
            "ACTIVITY",
            "ORIENTATION",
            "STEPS",
            "HEART_RATE",
            "SOUND_VOLUME",
            "RR",
            "PRESSURE",
            "RESPIRATION_RATE",
            "BODY_POSITION",
            "EDA",
            "GPS",
        };

        foreach (string name in names) {
            var value = dataType.GetStatic<AndroidJavaObject>(name);
            enumSet.Call<bool>("add", value);
        }

        return enumSet;
    }

    private static DisconnectReason ParseDisconnectReason(AndroidJavaObject reason) {
        if (reason == null) {
            return DisconnectReason.UnknownError;
        }

        try {
            int value = reason.Get<int>("value");
            return (DisconnectReason)value;
        } catch {
            return DisconnectReason.UnknownError;
        }
    }

    private static WearState ParseWearState(AndroidJavaObject wearState) {
        if (wearState == null) {
            return WearState.Unknown;
        }

        try {
            int value = wearState.Get<int>("value");
            return (WearState)value;
        } catch {
            return WearState.Unknown;
        }
    }

    private static ActivityType ParseActivityType(AndroidJavaObject activityType) {
        if (activityType == null) {
            return ActivityType.Unspecific;
        }

        try {
            int value = activityType.Get<int>("value");
            return (ActivityType)value;
        } catch {
            return ActivityType.Unspecific;
        }
    }

    private static BodyPosition ParseBodyPosition(AndroidJavaObject bodyPosition) {
        if (bodyPosition == null) {
            return BodyPosition.Unknown;
        }

        try {
            int value = bodyPosition.Get<int>("value");
            return (BodyPosition)value;
        } catch {
            return BodyPosition.Unknown;
        }
    }

    private static Exercise ParseExercise(AndroidJavaObject exercise) {
        if (exercise == null) {
            return Exercise.None;
        }

        try {
            int value = exercise.Get<int>("value");
            return (Exercise)value;
        } catch {
            return Exercise.None;
        }
    }

    private static SyncState ParseSyncState(AndroidJavaObject syncState) {
        if (syncState == null) {
            return SyncState.Unavailable;
        }

        try {
            int value = syncState.Get<int>("value");
            return (SyncState)value;
        } catch {
            return SyncState.Unavailable;
        }
    }

    // -- Android callbacks ------------------------------------------------------------------------

    private sealed class DeviceDelegateProxy : AndroidJavaProxy {
        private readonly AidlabAndroid outer;

        public DeviceDelegateProxy(AidlabAndroid outer)
            : base("com.aidlab.sdk.DeviceDelegate") {
            this.outer = outer;
        }

        void didConnect(AndroidJavaObject device) => outer.onConnected(device);

        void didDisconnect(AndroidJavaObject device, AndroidJavaObject disconnectReason) => outer.onDisconnected(disconnectReason);

        void didReceiveECG(AndroidJavaObject device, long timestamp, float value) =>
            outer.aidlabDelegate?.didReceiveECG(outer, timestamp, value);

        void didReceiveRespiration(AndroidJavaObject device, long timestamp, float value) =>
            outer.aidlabDelegate?.didReceiveRespiration(outer, timestamp, value);

        void didReceiveRespirationRate(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceiveRespirationRate(outer, timestamp, value);

        void didReceiveBatteryLevel(AndroidJavaObject device, int stateOfCharge) =>
            outer.aidlabDelegate?.didReceiveBatteryLevel(outer, stateOfCharge);

        void didReceiveSkinTemperature(AndroidJavaObject device, long timestamp, float value) =>
            outer.aidlabDelegate?.didReceiveSkinTemperature(outer, timestamp, value);

        void didReceiveAccelerometer(AndroidJavaObject device, long timestamp, float ax, float ay, float az) =>
            outer.aidlabDelegate?.didReceiveAccelerometer(outer, timestamp, ax, ay, az);

        void didReceiveGyroscope(AndroidJavaObject device, long timestamp, float qx, float qy, float qz) =>
            outer.aidlabDelegate?.didReceiveGyroscope(outer, timestamp, qx, qy, qz);

        void didReceiveMagnetometer(AndroidJavaObject device, long timestamp, float mx, float my, float mz) =>
            outer.aidlabDelegate?.didReceiveMagnetometer(outer, timestamp, mx, my, mz);

        void didReceiveQuaternion(AndroidJavaObject device, long timestamp, float qw, float qx, float qy, float qz) =>
            outer.aidlabDelegate?.didReceiveQuaternion(outer, timestamp, qw, qx, qy, qz);

        void didReceiveOrientation(AndroidJavaObject device, long timestamp, float roll, float pitch, float yaw) =>
            outer.aidlabDelegate?.didReceiveOrientation(outer, timestamp, roll, pitch, yaw);

        void didReceiveEDA(AndroidJavaObject device, long timestamp, float conductance) =>
            outer.aidlabDelegate?.didReceiveEDA(outer, timestamp, conductance);

        void didReceiveGPS(AndroidJavaObject device, long timestamp, double latitude, double longitude, double altitude,
                           float speed, float heading, float hdop) =>
            outer.aidlabDelegate?.didReceiveGPS(outer, timestamp, latitude, longitude, altitude, speed, heading, hdop);

        void didReceiveBodyPosition(AndroidJavaObject device, long timestamp, AndroidJavaObject bodyPosition) =>
            outer.aidlabDelegate?.didReceiveBodyPosition(outer, timestamp, ParseBodyPosition(bodyPosition));

        void didReceiveActivity(AndroidJavaObject device, long timestamp, AndroidJavaObject activity) =>
            outer.aidlabDelegate?.didReceiveActivity(outer, timestamp, ParseActivityType(activity));

        void didReceiveSteps(AndroidJavaObject device, long timestamp, long value) =>
            outer.aidlabDelegate?.didReceiveSteps(outer, timestamp, value);

        void didReceiveHeartRate(AndroidJavaObject device, long timestamp, int heartRate) =>
            outer.aidlabDelegate?.didReceiveHeartRate(outer, timestamp, heartRate);

        void didReceiveRr(AndroidJavaObject device, long timestamp, int rr) =>
            outer.aidlabDelegate?.didReceiveRr(outer, timestamp, rr);

        void didReceiveSoundVolume(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceiveSoundVolume(outer, timestamp, value);

        void didReceivePressure(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceivePressure(outer, timestamp, value);

        void pressureWearStateDidChange(AndroidJavaObject device, AndroidJavaObject wearState) =>
            outer.aidlabDelegate?.pressureWearStateDidChange(outer, ParseWearState(wearState));

        void wearStateDidChange(AndroidJavaObject device, AndroidJavaObject wearState) =>
            outer.aidlabDelegate?.wearStateDidChange(outer, ParseWearState(wearState));

        void didDetectExercise(AndroidJavaObject device, AndroidJavaObject exercise) =>
            outer.aidlabDelegate?.didDetectExercise(outer, ParseExercise(exercise));

        void didReceiveSignalQuality(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceiveSignalQuality(outer, timestamp, value);

        void syncStateDidChange(AndroidJavaObject device, AndroidJavaObject state) =>
            outer.aidlabDelegate?.syncStateDidChange(outer, ParseSyncState(state));

        void didReceiveUnsynchronizedSize(AndroidJavaObject device, int unsynchronizedSize, float syncBytesPerSecond) =>
            outer.aidlabDelegate?.didReceiveUnsynchronizedSize(outer, unsynchronizedSize, syncBytesPerSecond);

        void didReceivePastECG(AndroidJavaObject device, long timestamp, float value) =>
            outer.aidlabDelegate?.didReceivePastECG(outer, timestamp, value);

        void didReceivePastRespiration(AndroidJavaObject device, long timestamp, float value) =>
            outer.aidlabDelegate?.didReceivePastRespiration(outer, timestamp, value);

        void didReceivePastSkinTemperature(AndroidJavaObject device, long timestamp, float value) =>
            outer.aidlabDelegate?.didReceivePastSkinTemperature(outer, timestamp, value);

        void didReceivePastHeartRate(AndroidJavaObject device, long timestamp, int heartRate) =>
            outer.aidlabDelegate?.didReceivePastHeartRate(outer, timestamp, heartRate);

        void didReceivePastRr(AndroidJavaObject device, long timestamp, int rr) =>
            outer.aidlabDelegate?.didReceivePastRr(outer, timestamp, rr);

        void didReceivePastRespirationRate(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceivePastRespirationRate(outer, timestamp, value);

        void didReceivePastActivity(AndroidJavaObject device, long timestamp, AndroidJavaObject activity) =>
            outer.aidlabDelegate?.didReceivePastActivity(outer, timestamp, ParseActivityType(activity));

        void didReceivePastSteps(AndroidJavaObject device, long timestamp, long value) =>
            outer.aidlabDelegate?.didReceivePastSteps(outer, timestamp, value);

        void didReceivePastAccelerometer(AndroidJavaObject device, long timestamp, float ax, float ay, float az) =>
            outer.aidlabDelegate?.didReceivePastAccelerometer(outer, timestamp, ax, ay, az);

        void didReceivePastGyroscope(AndroidJavaObject device, long timestamp, float qx, float qy, float qz) =>
            outer.aidlabDelegate?.didReceivePastGyroscope(outer, timestamp, qx, qy, qz);

        void didReceivePastMagnetometer(AndroidJavaObject device, long timestamp, float mx, float my, float mz) =>
            outer.aidlabDelegate?.didReceivePastMagnetometer(outer, timestamp, mx, my, mz);

        void didReceivePastQuaternion(AndroidJavaObject device, long timestamp, float qw, float qx, float qy, float qz) =>
            outer.aidlabDelegate?.didReceivePastQuaternion(outer, timestamp, qw, qx, qy, qz);

        void didReceivePastOrientation(AndroidJavaObject device, long timestamp, float roll, float pitch, float yaw) =>
            outer.aidlabDelegate?.didReceivePastOrientation(outer, timestamp, roll, pitch, yaw);

        void didReceivePastEDA(AndroidJavaObject device, long timestamp, float conductance) =>
            outer.aidlabDelegate?.didReceivePastEDA(outer, timestamp, conductance);

        void didReceivePastGPS(AndroidJavaObject device, long timestamp, double latitude, double longitude, double altitude,
                               float speed, float heading, float hdop) =>
            outer.aidlabDelegate?.didReceivePastGPS(outer, timestamp, latitude, longitude, altitude, speed, heading, hdop);

        void didReceivePastBodyPosition(AndroidJavaObject device, long timestamp, AndroidJavaObject bodyPosition) =>
            outer.aidlabDelegate?.didReceivePastBodyPosition(outer, timestamp, ParseBodyPosition(bodyPosition));

        void didReceivePastSoundVolume(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceivePastSoundVolume(outer, timestamp, value);

        void didReceivePastPressure(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceivePastPressure(outer, timestamp, value);

        void didReceivePastSignalQuality(AndroidJavaObject device, long timestamp, int value) =>
            outer.aidlabDelegate?.didReceivePastSignalQuality(outer, timestamp, value);

        void didDetectPastUserEvent(long timestamp) =>
            outer.aidlabDelegate?.didDetectPastUserEvent(timestamp);

        void didReceiveError(string error) => outer.aidlabDelegate?.didReceiveError(error);

        void didReceivePayload(AndroidJavaObject device, string process, AndroidJavaObject payload) {
            byte[] data = Array.Empty<byte>();
            if (payload != null) {
                IntPtr rawPayload = payload.GetRawObject();
                data = AndroidJNIHelper.ConvertFromJNIArray<byte[]>(rawPayload);
            }
            outer.aidlabDelegate?.didReceivePayload(outer, process, data);
        }
    }

    private readonly AidlabDelegate aidlabDelegate;
    private AndroidJavaObject aidlabManager;
    private AndroidJavaObject device;
    private DeviceDelegateProxy deviceDelegateProxy;
    private bool deviceDetected;
}
