using System;
using System.Collections;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;

namespace TwitchInGames {
    public partial class Chat {
        public class Channel {
            public delegate void ReceiveCallback(Channel channel, string userName, string message);

            public string Name { get; }

            private IntPtr native;
            private readonly Chat chat;
            private Exception exception;

            internal Channel(IntPtr native, Chat chat, string name) {
                this.native = native;
                this.chat = chat;
                Name = name;
            }

            public Coroutine<object> Depart() {
                return new Coroutine<object>(InternalDepart());
            }

            private IEnumerator InternalDepart() {
                if(native != IntPtr.Zero) {
                    var intPtr = native;
                    var isDeparted = false;
                    Action depart = () => {
                        NativeMethods.DepartChannel(ref native);
                        isDeparted = true;
                    };
                    using(var thread = new SingleThread()) {
                        thread.EnqueueTask(depart);
                        yield return new WaitUntil(() => isDeparted);
                    }
                    chat.channels.Remove(intPtr);
                }
            }

            public Coroutine<object> SendLine(string message) {
                return new Coroutine<object>(InternalSendLine(message));
            }

            private IEnumerator InternalSendLine(string message) {
                if(native == IntPtr.Zero) {
                    throw new InvalidOperationException($"channel \"{Name}\" departed");
                }
                var isSent = false;
                Action send = () => {
                    try {
                        NativeMethods.SendLineToChannel(native, message);
                    } catch(Exception ex) {
                        exception = ex;
                    }
                    isSent = true;
                };
                using(var thread = new SingleThread()) {
                    exception = null;
                    thread.EnqueueTask(send);
                    yield return new WaitUntil(() => isSent || exception != null);
                }
                if(exception != null) {
                    throw exception;
                }
            }

            private static class NativeMethods {
                [DllImport(ImportName, CallingConvention = CallingConvention.Cdecl)]
                internal static extern void DepartChannel(ref IntPtr channel);

                [DllImport(ImportName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, PreserveSig = false)]
                internal static extern void SendLineToChannel(IntPtr channel, string message);
            }
        }
    }
}
