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

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import fr.insa.clavardator.client.users.PeerUser;
import fr.insa.clavardator.lib.users.UserState;
import fr.insa.clavardator.lib.util.ErrorCallback;
import fr.insa.clavardator.lib.util.Log;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;

import java.beans.PropertyChangeEvent;
import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.ResourceBundle;

/**
 * Controller for the chat input field and associated buttons
 */
public class ChatFooterController implements Initializable {
	FileChooser fileChooser = new FileChooser();
	File attachedFile;
	@FXML
	private HBox container;
	@FXML
	private JFXTextField textField;
	@FXML
	private JFXButton sendButton;
	@FXML
	private JFXButton attachButton;
	private ErrorCallback sendErrorListeners;
	private PeerUser remoteUser;
	private HashMap<PeerUser, String> savedText;

	public void setSendErrorListener(ErrorCallback listener) {
		sendErrorListeners = listener;
	}

	public void onAttachmentPress() {
		attachedFile = fileChooser.showOpenDialog(container.getScene().getWindow());
		sendButton.setDisable(!canSend());
		if (attachedFile != null) {
			setSendButtonActive();
		} else {
			removeAttachment();
		}
	}

	public void clearSendButton() {
		attachButton.getStyleClass().remove("attachment-ready");
	}

	public void setSendButtonActive() {
		clearSendButton();
		attachButton.getStyleClass().add("attachment-ready");
	}

	public void removeAttachment() {
		attachedFile = null;
		clearSendButton();
	}

	public void onSendError(Exception e) {
		Log.e(this.getClass().getSimpleName(), "Error: Could not send message", e);
		if (sendErrorListeners != null) {
			sendErrorListeners.onError(e);
		}
	}

	/**
	 * If the input text is not empty and the remote user set, send the message
	 */
	public void onSend() {
		if (canSend()) {
			if (remoteUser != null) {
				if (attachedFile == null) {
					remoteUser.sendTextMessage(textField.getText(), this::onSendError);
				} else {
					remoteUser.sendFileMessage(textField.getText(), attachedFile, this::onSendError);
					removeAttachment();
				}
			} else {
				Log.e(this.getClass().getSimpleName(), "Error: remote user not set");
			}
			textField.setText("");
		}
	}

	/**
	 * Enables or disables the chat controls
	 *
	 * @param enabled True to enable, false otherwise
	 */
	private void setEnabled(boolean enabled) {
		container.setDisable(!enabled);
	}

	/**
	 * Find saved input for the current user
	 *
	 * @return The saved text, or an empty string if none found
	 */
	private String findSavedText() {
		String text = null;
		if (remoteUser != null) {
			text = savedText.get(remoteUser);
		}
		return text != null ? text : "";
	}

	/**
	 * Save the give text for the current user.
	 * This is used to save input when jumping between users
	 *
	 * @param text The text to save
	 */
	private void saveText(String text) {
		if (remoteUser != null) {
			savedText.put(remoteUser, text);
		}
	}

	/**
	 * Saves text and checks if we should disable the send button on input change.
	 *
	 * @param observable The observed string
	 * @param oldText    The previous text
	 * @param newText    The new text
	 */
	public void onTextChange(ObservableValue<? extends String> observable, String oldText, String newText) {
		saveText(newText);
		sendButton.setDisable(!canSend());
	}

	/**
	 * Check if the text field is empty
	 *
	 * @return True if empty, false otherwise
	 */
	private boolean canSend() {
		return !textField.getText().isEmpty() || attachedFile != null;
	}

	/**
	 * Updates shown username and item color when user changes
	 *
	 * @param propertyChangeEvent The change event
	 */
	private void onUserStateChange(PropertyChangeEvent propertyChangeEvent) {
		final String propName = propertyChangeEvent.getPropertyName();
		if (propName.equals("state")) {
			final UserState newState = (UserState) propertyChangeEvent.getNewValue();
			Platform.runLater(() -> setEnabled(newState == UserState.CONNECTED));
		}
	}

	/**
	 * Sets the remote user to send messages to, and tries to find saved input.
	 *
	 * @param user The remote user to set
	 */
	public void setRemoteUser(PeerUser user) {
		if (this.remoteUser == null || !this.remoteUser.equals(user)) {
			if (this.remoteUser != null) {
				// remove old observer before setting new user
				this.remoteUser.removeObserver(this::onUserStateChange);
			}
			this.remoteUser = user;
			user.addObserver(this::onUserStateChange);
			textField.setText(findSavedText());
			removeAttachment();
			sendButton.setDisable(!canSend());
			setEnabled(user.isActive());
		}
	}

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		savedText = new HashMap<>();
		textField.textProperty().addListener(this::onTextChange);
		textField.setOnKeyPressed(event -> {
			if (event.getCode() == KeyCode.ENTER) {
				event.consume();
				onSend();
			}
		});
	}
}
