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

using namespace TwitchInGames;

#ifdef _DEBUG
namespace {
	void CheckResult_(string_t message, TTV_ErrorCode errorCode) {
		if(errorCode != TTV_EC_SUCCESS) {
			DebugWriteLine(_T("[%s] internal error: %d (%#x)"), message, errorCode, errorCode);
			throw TwitchException(errorCode);
		}
	}
}

# define CheckResult CheckResult_
#else
# define CheckResult(m,e) do { auto e_ = (e); if(e_ != TTV_EC_SUCCESS) throw TwitchException(e); } while(false)
#endif

TtvInitializer::TtvInitializer() {
	TTV_InitializeLibrary();
}

TtvInitializer::~TtvInitializer() {
	//TTV_ShutdownLibrary();
}

void ChatImpl::CoreApiListener::CoreUserLoginComplete(std::string const& /*oauthToken*/, ttv::UserId /*userId*/, TTV_ErrorCode /*ec*/) {}

void ChatImpl::CoreApiListener::CoreUserLogoutComplete(ttv::UserId /*userId*/, TTV_ErrorCode /*ec*/) {}

void ChatImpl::CoreApiListener::CoreUserAuthenticationIssue(ttv::UserId /*userId*/, std::string const& /*oauthToken*/, TTV_ErrorCode /*ec*/) {}

void ChatImpl::CoreApiListener::CorePubSubStateChanged(ttv::UserId /*userId*/, ttv::PubSubState /*state*/, TTV_ErrorCode /*ec*/) {}

void ChatImpl::CoreApiListener::ModuleStateChanged(ttv::IModule* /*source*/, ttv::IModule::State /*state*/, TTV_ErrorCode /*ec*/) {}

void ChatImpl::ChatApiListener::ChatUserEmoticonSetsChanged(ttv::UserId /*userId*/, std::vector<ttv::chat::EmoticonSet> const& /*emoticonSets*/) {}

void ChatImpl::ChatApiListener::ModuleStateChanged(ttv::IModule* /*source*/, ttv::IModule::State /*state*/, TTV_ErrorCode /*ec*/) {}

void ChatImpl::ChatChannelListener::ChatChannelStateChanged(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::ChatChannelState /*state*/, TTV_ErrorCode /*ec*/) {}

void ChatImpl::ChatChannelListener::ChatChannelInfoChanged(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::ChatChannelInfo const& /*channelInfo*/) {}

void ChatImpl::ChatChannelListener::ChatChannelRestrictionsChanged(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::ChatChannelRestrictions const& /*restrictions*/) {}

void ChatImpl::ChatChannelListener::ChatChannelLocalUserChanged(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::ChatUserInfo const& /*userInfo*/) {}

void ChatImpl::ChatChannelListener::ChatChannelMessagesReceived(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, std::vector<ttv::chat::LiveChatMessage> const& /*messageList*/) {}

void ChatImpl::ChatChannelListener::ChatChannelSubscriptionNoticeReceived(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::SubscriptionNotice const& /*notice*/) {}

void ChatImpl::ChatChannelListener::ChatChannelFirstTimeChatterNoticeReceived(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::FirstTimeChatterNotice const& /*notice*/) {}

void ChatImpl::ChatChannelListener::ChatChannelRaidNoticeReceived(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::RaidNotice const& /*notice*/) {}

void ChatImpl::ChatChannelListener::ChatChannelUnraidNoticeReceived(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::chat::UnraidNotice const& /*notice*/) {}

void ChatImpl::ChatChannelListener::ChatChannelMessagesCleared(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/) {}

void ChatImpl::ChatChannelListener::ChatChannelUserMessagesCleared(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, ttv::UserId /*clearUserId*/) {}

void ChatImpl::ChatChannelListener::ChatChannelHostTargetChanged(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, std::string const& /*targetChannelName*/, uint32_t /*numViewers*/) {}

void ChatImpl::ChatChannelListener::ChatChannelNoticeReceived(ttv::UserId /*userId*/, ttv::ChannelId /*channelId*/, std::string const& /*noticeId*/, std::map<std::string, std::string> const& /*params*/) {}

ChatImpl::ChatImpl() :
	apiListener(std::make_shared<ChatApiListener>()),
	channelListener(std::make_shared<ChatChannelListener>()),
	coreApi(std::make_shared<ttv::CoreAPI>()),
	chatApi(std::make_shared<ttv::chat::ChatAPI>()),
	isSignedIn(false) {
	CheckResult(_T("ChatImpl::ChatImpl"), coreApi->SetListener(std::make_shared<CoreApiListener>()));
}

ChatImpl::~ChatImpl() {
	try {
		SignOut();
	} catch(std::exception const& ex) {
		UNREFERENCED_PARAMETER(ex);
		DebugWriteLine(_T("warning: SignOut failed: %s"), ToTstring(ex.what()).c_str());
		// Ignore exceptions.
	} catch(...) {
		DebugWriteLine(_T("warning: SignOut failed"));
		// Ignore exceptions.
	}
}

std::shared_ptr<ttv::chat::IChatChannel> ChatImpl::CreateChannel(ttv::ChannelId channelId, std::shared_ptr<ttv::chat::IChatChannelListener> const& listener) {
	std::shared_ptr<ttv::chat::IChatChannel> channel;
	CheckResult(_T("ChatImpl::CreateChannel"), chatApi->CreateChatChannel(userId, channelId, listener, channel));
	return channel;
}

tstring ChatImpl::SignIn(tstring const& clientId_, tstring const& token_) {
	SignOut();

	// Get the user for its identifier.
	auto user = User::FetchCurrent(clientId_, token_).get();
	userId = _ttoi(user.Id.c_str());
	if(userId == 0) {
		DebugWriteLine(_T("[Chat::SignIn] No user for token"));
		throw TwitchException(FromPlatformError(ERROR_BAD_ARGUMENTS));
	}

	CheckResult(_T("ChatImpl::SignIn.ttv::SetClientId"), ttv::SetClientId(FromTstring(clientId_)));
	CheckResult(_T("ChatImpl::SignIn.coreApi->Initialize"), coreApi->Initialize([](TTV_ErrorCode ec) {
		CheckResult(_T("ChatImpl::SignIn.coreApi->Initialize(callback)"), ec);
	}));
	do {
		CheckResult(_T("ChatImpl::SignIn.coreApi->Update"), coreApi->Update());
	} while(coreApi->GetState() != ttv::IModule::State::Initialized);
	bool isLoggedIn = false;
	CheckResult(_T("ChatImpl::SignIn.coreApi->LogIn"), coreApi->LogIn(FromTstring(token_), [&isLoggedIn](TTV_ErrorCode ec, ttv::UserInfo const& /*userInfo*/) {
		if(ec != TTV_EC_SUCCESS) {
			DebugWriteLine(_T("[Chat::SignIn] internal error: %d"), ec);
			throw TwitchException(FromPlatformError(ERROR_BAD_ARGUMENTS));
		}
		isLoggedIn = true;
	}));
	do {
		std::this_thread::sleep_for(std::chrono::milliseconds(110));
		CheckResult(_T("ChatImpl::SignIn.coreApi->Update"), coreApi->Update());
	} while(!isLoggedIn);

	CheckResult(_T("ChatImpl::SignIn.chatApi->SetCoreApi"), chatApi->SetCoreApi(coreApi));
	CheckResult(_T("ChatImpl::SignIn.chatApi->SetListener"), chatApi->SetListener(apiListener));
	CheckResult(_T("ChatImpl::SignIn.chatApi->SetTokenizationOptions"), chatApi->SetTokenizationOptions({}));
	CheckResult(_T("ChatImpl::SignIn.chatApi->Initialize"), chatApi->Initialize([](TTV_ErrorCode) {}));
	do {
		CheckResult(_T("ChatImpl::SignIn.chatApi->Update"), chatApi->Update());
	} while(chatApi->GetState() != ttv::IModule::State::Initialized);
	std::promise<void> newPromise;
	updatePromise.swap(newPromise);
	updateTask = std::async(std::launch::async, [this] {
		auto updateFuture = updatePromise.get_future();
		while(updateFuture.wait_for(std::chrono::milliseconds(110)) == std::future_status::timeout) {
			CheckResult(_T("ChatImpl::SignIn.chatApi->Update"), chatApi->Update());
			CheckResult(_T("ChatImpl::SignIn.coreApi->Update"), coreApi->Update());
		}
	});
	clientId = clientId_;
	token = token_;
	isSignedIn = true;
	return user.Login;
}

void ChatImpl::SignOut() {
	if(isSignedIn) {
		updatePromise.set_value();
		updateTask.get();
		bool isShutDown = false;
		chatApi->Shutdown([&isShutDown](TTV_ErrorCode /*ec*/) {
			isShutDown = true;
		});
		do {
			chatApi->Update();
		} while(!isShutDown);
		isShutDown = false;
		coreApi->Shutdown([&isShutDown](TTV_ErrorCode /*ec*/) {
			isShutDown = true;
		});
		do {
			coreApi->Update();
		} while(!isShutDown);
	}
}
