#include "pch.h"
#include "Internal.h"
#include "ChatImpl.h"

using namespace TwitchInGames;

Chat::Chat() : chatImpl(std::make_shared<ChatImpl>()) {}

Chat::Chat(tstring const& clientId, tstring const& token) : Chat() {
	SignIn(clientId, token).get();
}

std::future<void> Chat::SignIn(tstring const& clientId_, tstring const& token_) {
	// Validate the arguments.
	if(clientId_.empty()) {
		DebugWriteLine(_T("[Chat::SignIn] Invalid client identifier"));
		throw TwitchException(FromPlatformError(ERROR_BAD_ARGUMENTS));
	}
	if(token_.empty()) {
		DebugWriteLine(_T("[Chat::SignIn] Invalid token"));
		throw TwitchException(FromPlatformError(ERROR_BAD_ARGUMENTS));
	}
	if(chatImpl->IsSignedIn) {
		DebugWriteLine(_T("[Chat::SignIn] Already signed in"));
		throw TwitchException(FromPlatformError(ERROR_INVALID_OPERATION));
	}

	// Perform sign-in.
	auto fn = [this, clientId_, token_] {
		login = chatImpl->SignIn(clientId_, token_);
		clientId = clientId_;
		token = token_;
	};
	return std::async(std::launch::deferred, fn);
}

Chat::~Chat() {
	if(chatImpl->IsSignedIn) {
		DebugWriteLine(_T("[Chat::~Chat] the chat is still open"));
		SignOut().get();
	}
}

std::future<void> Chat::SignOut() {
	auto fn = [this] {
		chatImpl->SignOut();
		SendUserScienceEvent(clientId, login, _T("sdk_chat_sign_out"), {});
	};
	return std::async(std::launch::deferred, fn);
}

std::future<std::shared_ptr<Chat::Channel>> Chat::JoinChannel(tstring const& channelName, Channel::ReceiveCallback receiveCallback) {
	auto fn = [this, channelName, receiveCallback] {
		// If the client already joined the channel, return it.
		auto currentChannel = LockedOperation(channelsAccess, [this, &channelName] {
			auto it = channels.find(channelName);
			if(it != channels.cend()) {
				// Check if the channel is live.
				auto currentChannel = it->second.lock();
				if(currentChannel) {
					DebugWriteLine(_T("[Chat::JoinChannel] Already joined this channel."));
					return currentChannel;
				} else {
					channels.erase(it);
				}
			}
			return std::shared_ptr<Channel>();
		});
		if(currentChannel) {
			return currentChannel;
		}

		// Create a channel and add it to the collection.
		return CreateChannel(channelName, receiveCallback);
	};
	return std::async(std::launch::deferred, fn);
}

std::shared_ptr<Chat::Channel> Chat::CreateChannel(tstring const& channelName, Channel::ReceiveCallback receiveCallback) {
	// Create a new channel wrapped in a smart pointer using a local structure
	// since std::make_shared<Channel> isn't available since the Channel
	// constructor is private.
	struct Creator : public Channel {
		Creator(Chat& chat, tstring const& name, ReceiveCallback receiveCallback) : Channel(chat, name, receiveCallback) {}
	};
	std::shared_ptr<Channel> channel = std::make_shared<Creator>(*this, channelName, receiveCallback);

	// Add it to the chat's channel collection.
	LockedOperation(channelsAccess, [this, &channelName, &channel] { channels.emplace(channelName, channel); });

	// Send a science event.
	SendUserScienceEvent(clientId, login, _T("sdk_chat_join_channel"), { { _T("chat_channel_name"), channelName } });

	return channel;
}

std::vector<std::shared_ptr<Chat::Channel>> Chat::GetChannels() const {
	// Return only live channels.
	return LockedOperation(const_cast<Chat*>(this)->channelsAccess, [this] {
		std::vector<std::shared_ptr<Channel>> rv;
		for(auto const& p : channels) {
			auto channel = p.second.lock();
			if(channel) {
				rv.emplace_back(std::move(channel));
			}
		}
		return rv;
	});
}
