package hsr.dsa.view.templates;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import hsr.dsa.blockchain.exceptions.SmartContractException;
import hsr.dsa.controller.ChatController;
import hsr.dsa.controller.ControllerManager;
import hsr.dsa.data.models.ChatMessage;
import hsr.dsa.data.models.LocalUser;
import hsr.dsa.data.models.NotaryMessage;
import hsr.dsa.data.models.RemoteUser;
import hsr.dsa.data.models.TextMessage;
import hsr.dsa.data.models.User;
import hsr.dsa.utils.Observable;
import hsr.dsa.utils.ObservableList;
import hsr.dsa.utils.Observer;
import hsr.dsa.view.FXController;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;

@SuppressWarnings("restriction")
public class ChatPaneController implements FXTemplate<RemoteUser>, Observer {
	private static final Logger log = LoggerFactory.getLogger(ChatPaneController.class);
	private final ChatController chatController;

	private RemoteUser friend;
	private ObservableList<ChatMessage> chatMessages;

	@FXML
	private VBox messages;
	@FXML
	private ScrollPane messagesScroll;
	@FXML
	private TextArea message;

	public ChatPaneController() throws IOException {
		this.chatController = ControllerManager.getChatController();
	}

	@FXML
	public void initialize() {
		this.messages.heightProperty().addListener(new ChangeListener<Number>() {
			@Override
			public void changed(ObservableValue<? extends Number> observable, Number oldvalue, Number newValue) {
				messagesScroll.setVvalue(1.0);
			}
		});
		if (friend != null) {
			this.loadChatHistory();
		}
	}

	private void loadChatHistory() {
		List<ChatMessage> messages = friend.getChatMessages();
		this.messages.getChildren().clear();
		for (ChatMessage message : messages) {
			if (message instanceof NotaryMessage) {
				NotaryMessage textMessage = (NotaryMessage) message;
				if (textMessage.getSender() instanceof LocalUser) {
					showMessage(textMessage, "/fxml/chatWindow/templates/userNotaryMessage.fxml");
				} else {
					showMessage(textMessage, "/fxml/chatWindow/templates/foreignNotaryMessage.fxml");
				}
			} else if (message instanceof TextMessage) {
				TextMessage textMessage = (TextMessage) message;
				if (textMessage.getSender() instanceof LocalUser) {
					showMessage(textMessage, "/fxml/chatWindow/templates/userMessage.fxml");
				} else {
					showMessage(textMessage, "/fxml/chatWindow/templates/foreignMessage.fxml");
				}
			}
		}
	}

	@Override
	public void setControler(FXController controller) {
	}

	@Override
	public void setData(RemoteUser friend) {
		this.friend = friend;
		this.chatMessages = this.friend.getChatMessages();
		this.chatMessages.addObserver(this);
		if (this.messages != null) {
			this.loadChatHistory();
		}
	}

	public void send() throws IOException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { // TODO  handle exceptions
		String message = this.message.getText().trim();
		if (message.length() > 0) {
			this.message.setText("");
			User user = this.chatController.getProfile();
			this.chatMessages.add(new TextMessage(user, LocalDateTime.now(), message));
			this.chatController.getClient().writeDirectMessage(user, this.friend, message);
		}
	}

	public void sendNotary() {
		String messageText = this.message.getText().trim();
		this.message.setDisable(true);
		(new Thread() {
			@Override
			public void run() {
				try {
					if (messageText.length() > 0) {
						User user = chatController.getProfile();
						chatMessages.add(new NotaryMessage(user, LocalDateTime.now(), messageText, MessageDigest.getInstance("SHA-256").digest(messageText.trim().getBytes(StandardCharsets.UTF_8))));
						chatController.getClient().writeNotaryMessage(user, friend, messageText);
					}
				} catch (NoSuchAlgorithmException | IOException | SmartContractException e) {
					log.error("unable to send notary message", e);
				} finally {
					Platform.runLater(() -> {
						message.setText("");
						message.setDisable(false);
					});
				}
			}
		}).start();
		
	}

	@SuppressWarnings("unchecked")
	private void showMessage(TextMessage message, String view) {
		try {
			this.message.setText("");
			FXMLLoader loader = new FXMLLoader();
			Parent messageView = (Parent) loader.load(getClass().getResourceAsStream(view));
			FXTemplate<TextMessage> templateControler = (FXTemplate<TextMessage>) loader.getController();
			templateControler.setData(message);
			this.messages.getChildren().add(messageView);
		} catch (IOException e) {
			log.error("unable to process message", e);
		}
	}

	@SuppressWarnings("unchecked")
	private void showMessage(NotaryMessage message, String view) {
		try {
			this.message.setText("");
			FXMLLoader loader = new FXMLLoader();
			Parent messageView = (Parent) loader.load(getClass().getResourceAsStream(view));
			FXTemplate<NotaryMessage> templateControler = (FXTemplate<NotaryMessage>) loader.getController();
			templateControler.setData(message);
			this.messages.getChildren().add(messageView);
		} catch (IOException e) {
			log.error("unable to process message", e);
		}
	}

	@Override
	public void onChange(Observable observable) {
		Platform.runLater(() -> {
			this.loadChatHistory();
		});
	}
}
