package fr.insa.clavardator.client.ui.dialogs;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.base.ValidatorBase;
import fr.insa.clavardator.client.ui.ButtonPressEvent;
import fr.insa.clavardator.client.users.CurrentUser;
import fr.insa.clavardator.client.users.UserList;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.StackPane;

import java.net.URL;
import java.util.ResourceBundle;

/**
 * Controller for the username edit dialog
 */
public class EditUsernameDialogController implements Initializable {

	@FXML
	private Label titleLabel;
	@FXML
	private Label descriptionLabel;
	@FXML
	private JFXDialog dialog;
	@FXML
	private JFXTextField textField;
	@FXML
	private JFXButton confirmButton;
	@FXML
	private JFXButton cancelButton;

	private State currentState;
	private Validator validator;
	private ButtonPressEvent successListener;
	private ButtonPressEvent cancelListener;
	private UserList userList;

	@FXML
	private void onConfirm() {
		setLocked(true);
		if (userList != null && userList.isUsernameAvailable(textField.getText())) {
			onSuccess();
		} else {
			onError();
		}
	}

	@FXML
	private void onCancel() {
		if (cancelListener != null) {
			cancelListener.onPress();
		}
		hide();
	}

	public void setUserList(UserList userList) {
		this.userList = userList;
	}

	public void setOnSuccessListener(ButtonPressEvent listener) {
		this.successListener = listener;
	}

	public void setOnCancelListener(ButtonPressEvent listener) {
		this.cancelListener = listener;
	}

	/**
	 * Plays the dialog show animation
	 *
	 * @param root The dialog's root component
	 * @param mode The dialog display mode
	 * @implNote WARNING: Do not try to open the dialog instantly after closing it.
	 * Due to JFoenix animations, the dialog will not reopen.
	 * If you must, wait at least 500ms before reopening.
	 */
	public void show(StackPane root, Mode mode) {
		setLocked(false);
		setFieldError(State.VALID);
		setMode(mode);
		textField.setText("");
		dialog.show(root);
	}

	/**
	 * Hides the dialog.
	 *
	 * @implNote WARNING: Do not try to open the dialog instantly after closing it.
	 * Due to JFoenix animations, the dialog will not reopen.
	 * If you must, wait at least 500ms before reopening.
	 */
	public void hide() {
		dialog.close();
	}

	/**
	 * Locks the dialog on screen or frees it.
	 *
	 * @param state True to lock, false to unlock
	 */
	private void setLocked(boolean state) {
		confirmButton.setDisable(state || textField.getText().isEmpty());
		cancelButton.setDisable(state);
		dialog.setOverlayClose(!state);
		textField.setDisable(state);
	}

	/**
	 * Sets the input error
	 *
	 * @param state The input state to set
	 */
	private void setFieldError(State state) {
		currentState = state;
		textField.validate();
	}

	/**
	 * Enables save button if username is not empty, and stop input at max length.
	 *
	 * @param observable The observed string
	 * @param oldText    The previous text
	 * @param newText    The new text
	 */
	public void onUsernameChange(ObservableValue<? extends String> observable, String oldText, String newText) {
		setFieldError(State.VALID);
		confirmButton.setDisable(newText.isEmpty());
		final int MAX_LENGTH = 16;
		if (textField.getText().length() > MAX_LENGTH) {
			String s = textField.getText().substring(0, MAX_LENGTH);
			textField.setText(s);
		}
	}

	/**
	 * Sets the dialog display mode
	 *
	 * @param mode The mode to set
	 */
	public void setMode(Mode mode) {
		switch (mode) {
			case INITIAL:
				dialog.setOverlayClose(false);
				cancelButton.setText("Quitter");
				titleLabel.setText("Nom d'utilisateur");
				descriptionLabel.setText("Choisissez votre nom d'utilisateur.\nVous pourrez le changer plus tard");
				break;
			case ERROR:
				dialog.setOverlayClose(false);
				cancelButton.setText("Hors ligne");
				titleLabel.setText("Nom d'utilisateur expiré");
				descriptionLabel.setText("Votre nom d'utilisateur n'est plus valide.\nMerci d'en choisir un autre.");
				break;
			case EDIT:
				dialog.setOverlayClose(true);
				cancelButton.setText("Annuler");
				titleLabel.setText("Changer le nom d'utilisateur");
				descriptionLabel.setText("Entrez votre nouveau nom d'utilisateur");
				break;
		}
	}

	/**
	 * Propagates the username change to active users and unlocks the dialog.
	 */
	private void onSuccess() {
		setFieldError(State.VALID);
		setLocked(false);
		hide();
		final String newName = textField.getText();
		CurrentUser.getInstance().setUsername(newName);
		if (successListener != null) {
			successListener.onPress();
		}
	}

	/**
	 * Sets the field error and unlocks the dialog
	 */
	private void onError() {
		validator.setMessage("Nom d'utilisateur déjà utilisé");
		setFieldError(State.INVALID);
		setLocked(false);
	}

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		validator = new Validator();
		currentState = State.VALID;
		setMode(Mode.INITIAL);
		textField.textProperty().addListener(this::onUsernameChange);
		textField.getValidators().add(validator);
		textField.setOnKeyPressed(event -> {
			if (event.getCode() == KeyCode.ENTER) {
				event.consume();
				if (!textField.getText().isEmpty()) {
					onConfirm();
				}
			}
		});
	}

	public enum Mode {
		INITIAL,
		ERROR,
		EDIT
	}

	enum State {
		VALID,
		INVALID
	}

	class Validator extends ValidatorBase {
		@Override
		protected void eval() {
			this.hasErrors = new ReadOnlyBooleanWrapper(currentState != State.VALID);
		}
	}
}
