import { SmartFox, SFSEvent, LogLevel, NetDebugLevel, ConfigData, EventParam, ClientDisconnectionReason, SFSObject, PasswordUtil } from 'sfs3-api';
import { LoginRequest, ExtensionRequest } from 'sfs3-api';

// IP address or domain name of the SmartFoxServer instance; if encryption is enabled, a domain name must be used
const host = "127.0.0.1";

// HTTP listening port of the SmartFoxServer instance
const httpPort = 8088;

// HTTPS listening port of the SmartFoxServer instance, used when connection encryption in enabled
const httpsPort = 8843;

// Enable SmartFoxServer protocol encryption; 'host' must be a domain name and an SSL certificate must have been deployed
const encrypt = false;

// Name of the SmartFoxServer Zone to join
const zone = "SignUp";

// Display SmartFoxServer client debug messages
const debug = false;

// Client-side SmartFoxServer logging level
const logLevel = LogLevel.INFO;

// These constants represent the three commands accepted by the SignUp Assistant
// to respectively register, activate and recover and account
const CMD_SUBMIT = '$SignUp.Submit';
const CMD_ACTIVATE = '$SignUp.Activate';
const CMD_RECOVER = '$SignUp.Recover';
const CMD_RESEND_EMAIL = '$SignUp.ResendEmail';

// These constants represent the application states	
const STATE_LOGIN = 'login';
const STATE_REGISTER = 'register';
const STATE_ACTIVATE = 'activate';
const STATE_RESEND_ACT = 'resendAct';
const STATE_RESET_PWD = 'resetPwd';

// Keep track of the current application state
let currentState;

let currentViewName;

let loginEmailInput;
let loginPasswordInput;
let loginErrorAlert;

let nameInput;
let registerEmailInput;
let passwordInput;
let confirmPasswordInput;
let countryInput;
let birthInput;
let registerErrorAlert;

let activationCodeInput;
let activateErrorAlert;
let activateSuccessAlert;
let resendCodeButton;

let resendActEmailInput;
let resendActErrorAlert;
let resendActSuccessAlert;

let resetPwdUserInput;
let resetPwdErrorAlert;
let resetPwdSuccessAlert;

let userLabel;
let sfs;

document.addEventListener('DOMContentLoaded', () => {
	userLabel = document.getElementById('userLabel');

	// Login panel
	loginEmailInput = document.getElementById('loginEmailInput');
	loginPasswordInput = document.getElementById('loginPasswordInput');
	loginErrorAlert = document.getElementById('loginErrorAlert');

	// Register panel
	nameInput = document.getElementById('nameInput');
	registerEmailInput = document.getElementById('registerEmailInput');
	passwordInput = document.getElementById('passwordInput');
	confirmPasswordInput = document.getElementById('confirmPasswordInput');
	countryInput = document.getElementById('countryInput');
	birthInput = document.getElementById('birthInput');
	registerErrorAlert = document.getElementById('registerErrorAlert');

	// Activate panel
	activationCodeInput = document.getElementById('activationCodeInput');
	activateErrorAlert = document.getElementById('activateErrorAlert');
	activateSuccessAlert = document.getElementById('activateSuccessAlert');
	resendCodeButton = document.getElementById('resendCodeButton');

	// Resend activation panel
	resendActEmailInput = document.getElementById('resendActEmailInput');
	resendActErrorAlert = document.getElementById('resendActErrorAlert');
	resendActSuccessAlert = document.getElementById('resendActSuccessAlert');

	// Reset password panel
	resetPwdUserInput = document.getElementById('resetPwdUserInput');
	resetPwdErrorAlert = document.getElementById('resetPwdErrorAlert');
	resetPwdSuccessAlert = document.getElementById('resetPwdSuccessAlert');

	//--------------------------------------------------------

	// Add login form submit listener
	const loginForm = document.getElementById('loginForm');
	loginForm.addEventListener('submit', onLoginFormSubmit);

	// Add register form submit listener
	const registerForm = document.getElementById('registerForm');
	registerForm.addEventListener('submit', onRegisterFormSubmit);

	// Add activate form submit listener
	const activateForm = document.getElementById('activateForm');
	activateForm.addEventListener('submit', onActivateFormSubmit);

	// Add resend code button click listener
	resendCodeButton.addEventListener('click', onResendCodeButtonClick);

	// Add resend activation form submit listener
	const resendActForm = document.getElementById('resendActForm');
	resendActForm.addEventListener('submit', onResendActFormSubmit);

	// Add reset password form submit listener
	const resetPwdForm = document.getElementById('resetPwdForm');
	resetPwdForm.addEventListener('submit', onResetPwdFormSubmit);

	// Add confirm password input listener
	confirmPasswordInput.addEventListener('input', (e) => {
		// Validate with the built-in constraints
		confirmPasswordInput.setCustomValidity('');
		if (!confirmPasswordInput.validity.valid)
			return;

		// Extend with a custom constraints
		if (confirmPasswordInput.value != passwordInput.value)
			confirmPasswordInput.setCustomValidity('Passwords not matching');
	});

	// Add links click listeners
	document.getElementById('registerLink').addEventListener('click', onRegisterLinkClick);
	document.getElementById('activateLink').addEventListener('click', onActivateLinkClick);
	document.getElementById('resendActLink').addEventListener('click', onResendActLinkClick);
	document.getElementById('resetPwdLink').addEventListener('click', onResetPwdLinkClick);

	const backLinks = document.getElementsByClassName('back-link');
	for (let backLink of backLinks)
		backLink.addEventListener('click', onBackLinkClick);

    // Add logout listener
	const logoutButton = document.getElementById('logoutButton');
	logoutButton.addEventListener('click', onLogoutButtonClick);

	//--------------------------------------------------------

	// Set initial state & view
	setInitialStateAndView();
});

//----------------------------------------------------------
// UI event listeners
//----------------------------------------------------------

function onRegisterLinkClick(e)
{
	e.preventDefault();

	// Clear fields
	nameInput.value = '';
	registerEmailInput.value = '';
	passwordInput.value = '';
	confirmPasswordInput.value = '';
	countryInput.value = '';
	birthInput.value = '';

	// Switch to register panel
	setApplicationState(STATE_REGISTER);
}

function onActivateLinkClick(e)
{
	e.preventDefault();

	// Show back link
	document.getElementById('activatePanel').querySelector('.back-link').hidden = false;

	// Hide resend code button (available during registration process only)
	resendCodeButton.hidden = true;

	// Clear field
	activationCodeInput.value = '';

	// Switch to activation panel
	setApplicationState(STATE_ACTIVATE);
}

function onResendActLinkClick(e)
{
	e.preventDefault();

	// Clear fields
	resendActEmailInput.value = '';

	// Switch to resend activation code panel
	setApplicationState(STATE_RESEND_ACT);
}

function onResetPwdLinkClick(e)
{
	e.preventDefault();

	// Clear fields
	resetPwdUserInput.value = '';

	// Switch to reset pasword panel
	setApplicationState(STATE_RESET_PWD);
}

function onBackLinkClick(e)
{
	e.preventDefault();

	// Switch to login panel
	setApplicationState(STATE_LOGIN);
}

/**
 * On Login form submit, connect to SmartFoxServer.
 */
function onLoginFormSubmit(e)
{
	e.preventDefault();

	// Disable submit
	enableSubmitButtons(false);

	// Connect & login
	connect();
}

/**
 * On Register form submit, connect to SmartFoxServer and send registration details.
 */
function onRegisterFormSubmit(e)
{
	e.preventDefault();

	// Hide previous error message
	registerErrorAlert.hidden = true;

	// Disable submit
	enableSubmitButtons(false);

	// Connect & login
	connect();
}

/**
 * On Activate form submit, connect to SmartFoxServer (unless already connected) and send activation code.
 */
function onActivateFormSubmit(e)
{
	e.preventDefault();

	// Hide previous error message
	activateErrorAlert.hidden = true;

	// Disable submit
	enableSubmitButtons(false);

	// Disable resend button
	resendCodeButton.disabled = true;

	// If already connected, send activation code directly
	// Client is already connected when activation panel is displayed during the registration process
	if (sfs != null && sfs.isConnected)
		sendActivationCode();

	// Connect & login
	// On successful login, activation code is sent to the server
	else
		connect();
}

/**
 * On Resend code button click, send request to resend activation code by email.
 */
function onResendCodeButtonClick(e)
{
	requestActivationCode();
}

/**
 * On Resend activation form submit, connect to SmartFoxServer and request to resend activation code by email.
 */
function onResendActFormSubmit(e)
{
	e.preventDefault();

	// Hide previous error message
	resendActErrorAlert.hidden = true;

	// Disable submit
	enableSubmitButtons(false);

	// Connect & login
	connect();
}

/**
 * On Reset password form submit, connect to SmartFoxServer and request to generate new password.
 */
function onResetPwdFormSubmit(e)
{
	e.preventDefault();

	// Hide previous error message
	resetPwdErrorAlert.hidden = true;

	// Disable submit
	enableSubmitButtons(false);

	// Connect & login
	connect();
}

/**
 * On Logout button click, disconnect from SmartFoxServer.
 */
function onLogoutButtonClick(e)
{
	sfs.disconnect();
}

//----------------------------------------------------------
// Helper methods
//----------------------------------------------------------

function trace(str)
{
	console.log('%c' + str, 'color: #6FBFF9');
}

function setInitialStateAndView()
{
	// Set application state
	setApplicationState(STATE_LOGIN);

	// Show access view
	showView('access');

	// Focus on username input
	loginEmailInput.focus();

	// Enable buttons
	enableSubmitButtons(true);
}

function setApplicationState(state)
{
	if (state == currentState)
		return;

	// Hide current panel
	if (currentState != null)
		document.getElementById(currentState + 'Panel').hidden = true;

	// Set current state
	currentState = state;

	// Show panel corresponding to new state
	document.getElementById(state + 'Panel').hidden = false;

	// Hide all alerts
	const alerts = document.getElementById('accessView').querySelectorAll('.alert');
	alerts.forEach(alert => {
		alert.hidden = true;
	});
}

function showView(viewName)
{
	if (currentViewName != null && currentViewName != viewName)
		document.getElementById(currentViewName + 'View').hidden = true;

	document.getElementById(viewName + 'View').hidden = false;

	currentViewName = viewName;
}

function enableSubmitButtons(enable)
{
	const submitButtons = document.querySelectorAll('button[type="submit"]');
	submitButtons.forEach(button => {
		button.disabled = !enable;
	});
}

function setErrorMessage(msg)
{
	if (msg == null || msg.length == 0)
	{
		loginErrorAlert.innerHTML = '';
		loginErrorAlert.hidden = true;
	}
	else
	{
		loginErrorAlert.innerHTML = msg;
		loginErrorAlert.hidden = false;
	}
}

function sendActivationCode()
{
	const sfso = new SFSObject();
	sfso.putString('act_code',  activationCodeInput.value);
	
	sfs.send(new ExtensionRequest(CMD_ACTIVATE, sfso));
}

function requestActivationCode(email = null)
{
	let sfso = null;

	if (email != null && email != '')
	{
		sfso = new SFSObject();

		// The key used here must correspond to the value set for SignUpConfiguration.emailField on the server side (default: "email")
		sfso.putString('email',  email);
	}

	sfs.send(new ExtensionRequest(CMD_RESEND_EMAIL, sfso));
}

function connect()
{
	trace('Attempting connection to SmartFoxServer...');

	// Clear any previour error message
	setErrorMessage();

	// Initialize SmartFox client
	sfs = new SmartFox();

	// Add event listeners
	sfs.addEventListener(SFSEvent.CONNECTION, onConnection);
	sfs.addEventListener(SFSEvent.CONNECTION_LOST, onConnectionLost);
	sfs.addEventListener(SFSEvent.LOGIN, onLogin);
	sfs.addEventListener(SFSEvent.LOGIN_ERROR, onLoginError);
	sfs.addEventListener(SFSEvent.EXTENSION_RESPONSE, onExtensionResponse);

	// Configure internal logger
	sfs.logger.level = logLevel

	// Set connection parameters
	const cfg = new ConfigData();
	cfg.host = host;
	cfg.port = encrypt ? httpsPort : httpPort;
	cfg.useSSL = encrypt;
	cfg.zone = zone;
	cfg.netDebugLevel = debug ? NetDebugLevel.PROTOCOL : NetDebugLevel.OFF;

	// Connect to SmartFoxServer
	sfs.connect(cfg);
}

function login()
{
	trace('Attempting login...');

	switch (currentState)
	{
		// We're logging in as registered user, send the credentials
		case STATE_LOGIN:
			const email = loginEmailInput.value;
			let password = loginPasswordInput.value;

			// Hash password
			// If PLAIN_TEXT password mode is set on the SignUpAssistantComponent on the server side, comment the line below
			password = PasswordUtil.sha256Password(password);

			sfs.send(new LoginRequest(email, password));
			break;
		
			// All other cases: login as guest
		default:
			sfs.send(new LoginRequest());
	}
}

//----------------------------------------------------------
// SmartFoxServer event listeners
//----------------------------------------------------------

function onConnection(evtParams)
{
	const success = evtParams[EventParam.Success];

	// Check if the conenction was established or not
	if (success)
	{
		trace("Connection established successfully");
		trace("SFS3 API version: " + sfs.version);

		// Login
		login();
	}
	else
	{
		// Set initial state & view
		setInitialStateAndView();

		// Show error message
		setErrorMessage('Connection failed; is the server running at all?');
	}
}

function onConnectionLost(evtParams)
{
	// Set initial state & view
	setInitialStateAndView();

	// Show error message
	const reason = evtParams[EventParam.DisconnectionReason];

	trace(`Connection to SmartFoxServer lost; reason is: ${reason}`);

	if (reason != ClientDisconnectionReason.MANUAL)
	{
		// Show error message
		let connLostMsg = 'An unexpected disconnection occurred; ';

		switch (reason)
		{
			case ClientDisconnectionReason.IDLE:
				connLostMsg += 'you have been idle for too much time.';
				break;

			case ClientDisconnectionReason.KICKED:
				connLostMsg += 'you have been kicked.';
				break;

			case ClientDisconnectionReason.BANNED:
				connLostMsg += 'you have been banned.';
				break;

			default:
				connLostMsg += 'reason is unknown.';
		}

		setErrorMessage(connLostMsg);
	}
}

function onLogin(evtParams)
{
	trace("Login successful");

	// Succesful login, go to main appplication view
	if (currentState == STATE_LOGIN)
	{
		// Display user name
		userLabel.innerText = sfs.mySelf.name;

		// Show main view
		showView('main');

		// Reset state
		currentState = null;
	}
	
	// Send custom registration data to the server-side SignUpAssistant
 	// Each field name in the SFSObject must match the corresponding field name in the database
	else if (currentState == STATE_REGISTER)
	{
		const sfso = new SFSObject();
		sfso.putString('username', nameInput.value);
		sfso.putString('password', passwordInput.value);
		sfso.putString('email', registerEmailInput.value);
		sfso.putString('country', countryInput.value);
		sfso.putInt('birth_year', Number(birthInput.value));
		
		sfs.send(new ExtensionRequest(CMD_SUBMIT, sfso));
	}
	
	// Send activation code to the server-side SignUpAssistant
	// The field name in the SFSObject must match the corresponding field name in the database
	else if (currentState == STATE_ACTIVATE)
	{
		sendActivationCode();
	}
	
	// Send request to the server-side SignUpAssistant to resend the activation code
	else if (currentState == STATE_RESEND_ACT)
	{
		requestActivationCode(resendActEmailInput.value);
	}
	
	// Send request to the server-side SignUpAssistant to generate new password
	else if (currentState == STATE_RESET_PWD)
	{
		const sfso = new SFSObject();
		sfso.putString('username', resetPwdUserInput.value);

		sfs.send(new ExtensionRequest(CMD_RECOVER, sfso));
	}
}

function onLoginError(evtParams)
{
	trace("Login failed");

	// Disconnect
	// NOTE: this causes a CONNECTION_LOST event with reason "manual", which in turn removes all SFS listeners
	sfs.disconnect();

	// Show error message
	setErrorMessage(`Login failed due to the following error: ${evtParams[EventParam.ErrorMessage]}`);
}

function onExtensionResponse(evtParams)
{
	const cmd = evtParams[EventParam.Cmd];
	const params = evtParams[EventParam.ExtParams];
	
	// Server has replied to our Submit request
	if (cmd == CMD_SUBMIT)
	{
		if (params.containsKey('errorMessage'))
		{
			// Show error message
			registerErrorAlert.hidden = false;
			registerErrorAlert.innerText = params.getString('errorMessage');
		}
		else if (params.containsKey('success'))
		{
			// Hide back link
			document.getElementById('activatePanel').querySelector('.back-link').hidden = true;

			// Show resend code button (available during registration process only)
			resendCodeButton.hidden = false;

			// Switch to activation
			setApplicationState(STATE_ACTIVATE);
		}

		// Re-enable submit button
		enableSubmitButtons(true);
	}
	
	// Server has replied to our Activation request
	else if (cmd == CMD_ACTIVATE)
	{
		if (params.containsKey('errorMessage'))
		{
			// Show error message
			activateErrorAlert.innerText = params.getString('errorMessage');
			activateErrorAlert.hidden = false;

			// Re-enable submit button
			enableSubmitButtons(true);

			// Re-enale resend button
			resendCodeButton.disabled = false;
		}
		else if (params.containsKey('success'))
		{
			// Show success message
			activateSuccessAlert.innerText = 'Account activation completed, you can now login';
			activateSuccessAlert.hidden = false

			setTimeout(() => {
				// Re-enable submit button
				enableSubmitButtons(true);

				// Re-enale resend button
				resendCodeButton.disabled = false;

				// Disconnect (and go back to login)
				if (sfs != null && sfs.isConnected)
					sfs.disconnect();
			}, 2000);
		}
	}
	
	// Server has replied to our request to resend activation code
	else if (cmd == CMD_RESEND_EMAIL)
	{
		if (params.containsKey('errorMessage'))
		{
			// Show error message
			resendActErrorAlert.innerText = params.getString('errorMessage');
			resendActErrorAlert.hidden = false;

			// Re-enable submit button
			enableSubmitButtons(true);
		}
		else if (params.containsKey('success'))
		{
			// Show success message
			resendActSuccessAlert.innerText = 'Activation code sent, check your mailbox';
			resendActSuccessAlert.hidden = false;

			setTimeout(() => {
				// Re-enable submit button
				enableSubmitButtons(true);

				// Disconnect (and go back to login)
				if (sfs != null && sfs.isConnected)
					sfs.disconnect();
			}, 2000);
		}
	}
	
	// Server has replied to our request to reset password
	else if (cmd == CMD_RECOVER)
	{
		if (params.containsKey('errorMessage'))
		{
			// Show error message
			resetPwdErrorAlert.innerText = params.getString('errorMessage');
			resetPwdErrorAlert.hidden = false;

			// Re-enable submit button
			enableSubmitButtons(true);
		}
		else if (params.containsKey('success'))
		{
			// Show success message
			resetPwdSuccessAlert.innerText = 'New password sent, check your mailbox';
			resetPwdSuccessAlert.hidden = false;

			setTimeout(() => {
				// Re-enable submit button
				enableSubmitButtons(true);

				// Disconnect (and go back to login)
				if (sfs != null && sfs.isConnected)
					sfs.disconnect();
			}, 2000);
		}
	}
}