package uk.mgrove.ac.soton.comp1206.scene;

import javafx.application.Platform;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.mgrove.ac.soton.comp1206.component.Chat;
import uk.mgrove.ac.soton.comp1206.ui.GamePane;
import uk.mgrove.ac.soton.comp1206.ui.GameWindow;

import java.util.Timer;
import java.util.TimerTask;

public class LobbyScene extends BaseScene {

  /**
   * Logger
   */
  private static final Logger logger = LogManager.getLogger(LobbyScene.class);

  /**
   * Container for all channels
   */
  private final VBox allChannels = new VBox();

  /**
   * Container for controls to create new channel
   */
  private final VBox addChannelContainer = new VBox();

  /**
   * Button to open prompt to create new channel
   */
  private final Text promptToCreateChannel = new Text("Create channel");

  private final TextField newChannelName = new TextField();

  /**
   * Button to create new channel
   */
  private final Text createChannel = new Text("Create");

  /**
   * Container to list all users in channel
   */
  private final HBox channelUsers = new HBox();

  /**
   * Main content of scene (i.e. current channel actions)
   */
  private final VBox mainContent = new VBox();

  /**
   * Current channel
   */
  private String currentChannel;

  /**
   * Whether use is host of current channel
   */
  private boolean isChannelHost = false;

  /**
   * Chat container
   */
  private Chat chat;

  /**
   * Container for control buttons when in a channel
   */
  private final HBox channelFunctionButtons = new HBox();

  /**
   * Create a new scene, passing in the GameWindow the scene will be displayed in
   *
   * @param gameWindow the game window
   */
  public LobbyScene(GameWindow gameWindow) {
    super(gameWindow);
  }

  /**
   * Build the layout of the scene
   */
  @Override
  public void build() {
    logger.info("Building " + this.getClass().getName());

    root = new GamePane(gameWindow.getWidth(), gameWindow.getHeight());

    var menuPane = new StackPane();
    menuPane.setMaxWidth(gameWindow.getWidth());
    menuPane.setMaxHeight(gameWindow.getHeight());
    menuPane.getStyleClass().add("menu-background");
    root.getChildren().add(menuPane);

    var mainPane = new BorderPane();
    menuPane.getChildren().add(mainPane);

    //Awful title
    var title = new Text("TetrECS Lobby");
    BorderPane.setAlignment(title, Pos.TOP_CENTER);
    title.getStyleClass().add("title");
    mainPane.setTop(title);

    var leftMenu = new VBox();
    leftMenu.setSpacing(8);
    BorderPane.setMargin(leftMenu, new Insets(0,30,0,0));

    createChannel.setOnMouseClicked(this::createChannel);
    createChannel.getStyleClass().add("channelItem");
    newChannelName.setOnAction(this::createChannel);
    newChannelName.setPromptText("Channel name");
    promptToCreateChannel.setOnMouseClicked((event) -> {
      addChannelContainer.getChildren().clear();
      addChannelContainer.getChildren().addAll(newChannelName, createChannel);
      newChannelName.requestFocus();
    });
    promptToCreateChannel.getStyleClass().add("channelItem");
    addChannelContainer.getChildren().add(promptToCreateChannel);
    addChannelContainer.setSpacing(4);

    allChannels.setSpacing(4);
    var channelsTitle = new Text("Channels");
    channelsTitle.getStyleClass().add("heading");
    leftMenu.getChildren().addAll(channelsTitle, allChannels, addChannelContainer);

    chat = new Chat(gameWindow.getCommunicator(), false);

    mainContent.setSpacing(12);

    mainPane.setLeft(leftMenu);
    mainPane.setCenter(mainContent);

    gameWindow.getCommunicator().addListener(this::handleCommunicatorMessage);

    var listChannelsRequest = new Timer("Request list of channels from communicator");
    listChannelsRequest.schedule(new TimerTask() {
      @Override
      public void run() {
        gameWindow.getCommunicator().send("LIST");
      }
    }, 0, 10000);

    channelUsers.setSpacing(8);
    channelFunctionButtons.setSpacing(8);
  }

  /**
   * Initialise this scene. Called after creation
   */
  @Override
  public void initialise() {
    logger.info("Initialising Lobby");

    // exit lobby when escape key pressed
    scene.setOnKeyPressed((event) -> {
      if (event.getCode() == KeyCode.ESCAPE) {
        gameWindow.startMenu();
      }
    });

    getScene().addPostLayoutPulseListener(this::scrollChatToBottom);
  }

  /**
   * Scroll to bottom of messages list.
   */
  private void scrollChatToBottom() {
    chat.scrollToBottom();
  }

  /**
   * Handle incoming message from communicator
   *
   * @param message incoming message
   */
  private void handleCommunicatorMessage(String message) {
    if (message.startsWith("CHANNELS ")) {
      logger.info("List of channels received from communicator");
      var data = message.replaceFirst("CHANNELS ", "").split("\n");
      Platform.runLater(() -> {
        allChannels.getChildren().clear();
        for (var channel : data) {
          if (!channel.isBlank()) {
            var channelButton = new Text(channel);
            channelButton.getStyleClass().add("channelItem");
            channelButton.setOnMouseClicked((event) -> {
              if (currentChannel != null) gameWindow.getCommunicator().send("PART");
              gameWindow.getCommunicator().send("JOIN " + channel);
            });
            allChannels.getChildren().add(channelButton);
          }
        }
      });
    } else if (message.startsWith("USERS ")) {
      logger.info("List of users in channel received from communicator");
      var data = message.replaceFirst("USERS ", "").split("\n");
      Platform.runLater(() -> {
        channelUsers.getChildren().clear();
        var usersTitle = new Text("Users:");
        channelUsers.getChildren().add(usersTitle);
        for (var user : data) {
          var newUser = new Text(user);
          newUser.setId(user);
          channelUsers.getChildren().add(newUser);
        }
      });
    } else if (message.equals("PARTED")) {
      logger.info("Left channel: {}", currentChannel);
      Platform.runLater(() -> {
        gameWindow.getCommunicator().send("LIST");
        mainContent.getChildren().clear();
        chat.clearMessages();
        channelFunctionButtons.getChildren().clear();
        isChannelHost = false;
        currentChannel = null;
      });
    } else if (message.equals("HOST")) {
      logger.info("User is host of current channel");
      isChannelHost = true;
      Platform.runLater(() -> {
        logger.info("Current channel host status: {}", isChannelHost);
        var startGame = new Text("Start game");
        startGame.getStyleClass().add("channelItem");
        startGame.setOnMouseClicked((event) -> gameWindow.getCommunicator().send("START"));
        channelFunctionButtons.getChildren().add(0, startGame);
      });
    } else if (message.matches("^NICK .+:.+$")) {
      Platform.runLater(() -> {
        var usernames = message.replaceFirst("NICK ", "").split(":");
        if (channelUsers.getChildren().removeIf(user -> user.getId().equals(usernames[0]))) {
          var newUser = new Text(usernames[1]);
          newUser.setId(usernames[1]);
          channelUsers.getChildren().add(newUser);
        }
      });
    } else if (message.startsWith("JOIN ")) {
      Platform.runLater(() -> joinChannel(message.replaceFirst("JOIN ", "")));
    } else if (message.startsWith("ERROR ")) {
      var errorText = message.replaceFirst("ERROR ", "");
      logger.info("Received an error: {}", errorText);
      Platform.runLater(() -> {
        var error = new Alert(Alert.AlertType.ERROR, "Can't perform that action:\n\n" + errorText);
        error.showAndWait();
      });
    } else if (message.equals("START")) {
      logger.info("Starting game");
      Platform.runLater(gameWindow::startMultiplayerGame);
    }
  }

  /**
   * Create new channel on action event
   *
   * @param event triggering event (button click or text field submit)
   */
  private void createChannel(Event event) {
    if (currentChannel != null) gameWindow.getCommunicator().send("PART");
    gameWindow.getCommunicator().send("CREATE " + newChannelName.getText());
    gameWindow.getCommunicator().send("LIST");
    newChannelName.clear();
    addChannelContainer.getChildren().clear();
    addChannelContainer.getChildren().add(promptToCreateChannel);
  }

  /**
   * Join channel by name
   *
   * @param channelName name of channel
   */
  private void joinChannel(String channelName) {
    logger.info("Joined channel: {}", channelName);
    var leaveChannel = new Text("Leave channel");
    leaveChannel.getStyleClass().add("channelItem");
    leaveChannel.setOnMouseClicked((event) -> gameWindow.getCommunicator().send("PART"));
    channelFunctionButtons.getChildren().add(leaveChannel);

    var channelTitle = new Text(channelName);
    channelTitle.getStyleClass().add("heading");

    mainContent.getChildren().addAll(channelTitle, channelFunctionButtons, channelUsers, new Separator(), chat);
    chat.focusInputField();

    currentChannel = channelName;
  }

}
