using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace TwitchInGames {
    public partial class Chat : MonoBehaviour {
        class AndroidChannelReceiveCallBack : AndroidJavaProxy {
            Channel channel;
            public AndroidChannelReceiveCallBack(Channel channel) : base("tv.twitch.twiglib.ReceiveCallBack") {
                this.channel = channel;
            }
            public void invoke(string userName, string message) {
                this.channel.receiveCallback(this.channel, userName, message);
            }
        }

        public AndroidJavaObject chatJavaObject { get; private set; }
        public Dictionary<string, Channel> Channels => new Dictionary<string, Channel>(channels);
        public string Login { get; private set; }

        private Dictionary<string, Channel> channels = new Dictionary<string, Channel>();

        void Awake() {
            chatJavaObject = new AndroidJavaObject("tv.twitch.twiglib.Chat");
            if (chatJavaObject == null)
                Debug.LogError("Twitch Android Plugin is not set properly.");
        }

        public Coroutine<object> SignIn(string clientId, string token) {
            return new Coroutine<object>(InternalSignIn(clientId, token));
        }

        private IEnumerator InternalSignIn(string clientId, string token) {
            // Validate the arguments.
            Utility.CheckArgument(nameof(clientId), clientId);
            Utility.CheckArgument(nameof(token), token);
            if(chatJavaObject.Call<bool>("hasSignIn")) {
                throw new InvalidOperationException("Already signed in");
            }

            chatJavaObject.Call("SignIn", clientId, token);
            yield return new WaitUntil(() => chatJavaObject.Call<bool>("hasSignIn"));
        }

        public IEnumerator SignOut() {
            return new Coroutine<object>(InternalSignOut());
        }

        private IEnumerator InternalSignOut() {
            // Check for active channels.
            if (channels.Any()) {
                var message = $"{channels.Count} channels are still open";
                Debug.LogWarning(Utility.FormatMessage("Chat.SignOut", message));
            }

            chatJavaObject.Call("SignOut");
            yield return new WaitUntil(() => !chatJavaObject.Call<bool>("hasSignIn"));
            channels.Clear();
        }

        public Coroutine<Channel> JoinChannel(string channelName, Channel.ReceiveCallback receiveCallback) {
            return new Coroutine<Channel>(InternalJoinChannel(channelName, receiveCallback));
        }

        private IEnumerator InternalJoinChannel(string channelName, Channel.ReceiveCallback receiveCallback) {
            // Validate the arguments.
            Utility.CheckArgument(nameof(channelName), channelName);
            Utility.CheckArgument(nameof(receiveCallback), receiveCallback);
            channelName = channelName.ToLower();

            // If the client already joined the channel, return it.
            Channel currentChannel;
            if(channels.TryGetValue(channelName, out currentChannel)) {
                var message = $"Already joined channel \"{channelName}\".";
                Debug.LogWarning(Utility.FormatMessage("Chat.JoinChannel", message));
                yield return currentChannel;
            } else {
                // Create a channel and add it to the collection.
                var newChannel = new Channel(this, channelName, receiveCallback);
                channels.Add(channelName, newChannel);
                chatJavaObject.Call("Join", channelName, new AndroidChannelReceiveCallBack(newChannel));
                yield return new WaitUntil(() => chatJavaObject.Call<bool>("hasJoin", channelName));
                yield return newChannel;
            }
        }

        private void OnDestroy() {
            StartCoroutine(SignOut());
        }
    }
}
