using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

namespace Aidlab
{
    public static class Data
    {
        public abstract class DataDelegates
        {
            protected string name;

            public DataDelegates(string name)
            {
                this.name = name;
            }

            private readonly UnityEvent onDataReceivedEvents = new UnityEvent();

            /// <summary>
            /// Calls <paramref name="action"/> on the main thread for every sample, in capture order; during the call the
            /// fields hold that sample.
            /// </summary>
            public void Subscribe(UnityAction action) { onDataReceivedEvents.AddListener(action); }
            public void Unsubscribe(UnityAction action) { onDataReceivedEvents.RemoveListener(action); }

            protected void NotifySubscribers()
            {
                onDataReceivedEvents.Invoke();
            }
        }

        // Delivers each sample that arrives on any thread on the main thread. Consecutive samples of a signal share one
        // dispatch, so they keep their order among other signals and events.
        internal sealed class SampleQueue<TSample>
        {
            private readonly object sync = new object();
            private readonly Action<TSample> deliver;
            private Action dispatch;
            private List<TSample> batch;

            public SampleQueue(Action<TSample> deliver)
            {
                this.deliver = deliver;
            }

            public void Add(TSample sample)
            {
                AidlabSDK.DebugNotifySignalUpdated();
                lock (sync)
                {
                    if (MainThreadWorker.TryAddToLast(dispatch, batch, sample))
                        return;

                    var samples = new List<TSample> { sample };
                    dispatch = () => Deliver(samples);
                    batch = samples;
                    MainThreadWorker.Enqueue(dispatch);
                }
            }

            private void Deliver(List<TSample> samples)
            {
                foreach (TSample sample in samples)
                {
                    try
                    {
                        deliver(sample);
                    }
                    catch (Exception exception)
                    {
                        Debug.LogException(exception);
                    }
                }
            }
        }


        public class Data1<T> : DataDelegates
        {
            public T value;
            public System.UInt64 timestamp;
            private readonly SampleQueue<(T, System.UInt64)> samples;

            public Data1(string name) : base(name)
            {
                samples = new SampleQueue<(T, System.UInt64)>(sample =>
                {
                    (value, timestamp) = sample;
                    NotifySubscribers();
                });
            }

            public void ReceiveData(T value, System.UInt64 timestamp)
            {
                samples.Add((value, timestamp));
            }
        }

        public class Data3<T> : DataDelegates
        {
            public T x;
            public T y;
            public T z;
            public System.UInt64 timestamp;
            private readonly SampleQueue<(T, T, T, System.UInt64)> samples;

            public Data3(string name) : base(name)
            {
                samples = new SampleQueue<(T, T, T, System.UInt64)>(sample =>
                {
                    (x, y, z, timestamp) = sample;
                    NotifySubscribers();
                });
            }

            public void ReceiveData(T x, T y, T z, System.UInt64 timestamp)
            {
                samples.Add((x, y, z, timestamp));
            }
        }

        public class Data2<T1, T2> : DataDelegates
        {
            public T1 a;
            public T2 b;
            public System.UInt64 timestamp;
            private readonly SampleQueue<(T1, T2, System.UInt64)> samples;

            public Data2(string name) : base(name)
            {
                samples = new SampleQueue<(T1, T2, System.UInt64)>(sample =>
                {
                    (a, b, timestamp) = sample;
                    NotifySubscribers();
                });
            }

            public void ReceiveData(T1 a, T2 b, System.UInt64 timestamp)
            {
                samples.Add((a, b, timestamp));
            }
        }

        public class Data4<T, W> : DataDelegates
        {
            public T x;
            public T y;
            public T z;
            public W w;
            public System.UInt64 timestamp;
            private readonly SampleQueue<(T, T, T, W, System.UInt64)> samples;

            public Data4(string name) : base(name)
            {
                samples = new SampleQueue<(T, T, T, W, System.UInt64)>(sample =>
                {
                    (x, y, z, w, timestamp) = sample;
                    NotifySubscribers();
                });
            }

            public void ReceiveData(T x, T y, T z, W w, System.UInt64 timestamp)
            {
                samples.Add((x, y, z, w, timestamp));
            }
        }

        public class Data6 : DataDelegates
        {
            public float a;
            public float b;
            public float c;
            public float d;
            public float e;
            public float f;
            public System.UInt64 timestamp;
            private readonly SampleQueue<(float, float, float, float, float, float, System.UInt64)> samples;

            public Data6(string name) : base(name)
            {
                samples = new SampleQueue<(float, float, float, float, float, float, System.UInt64)>(sample =>
                {
                    (a, b, c, d, e, f, timestamp) = sample;
                    NotifySubscribers();
                });
            }

            public void ReceiveData(float a, float b, float c, float d, float e, float f, System.UInt64 timestamp)
            {
                samples.Add((a, b, c, d, e, f, timestamp));
            }
        }

        // Every event reaches the subscribers on the main thread.
        public class Events<T>
        {
            private readonly UnityEvent<T> listeners = new UnityEvent<T>();

            public void Subscribe(UnityAction<T> action) { listeners.AddListener(action); }
            public void Unsubscribe(UnityAction<T> action) { listeners.RemoveListener(action); }

            public void Receive(T value)
            {
                AidlabSDK.DebugNotifySignalUpdated();
                AidlabSDK.EnqueueMainThreadEvent(() => listeners.Invoke(value));
            }
        }

    }
}
