package de.siphalor.was.visual;

import de.siphalor.was.WhatAStorage;
import de.siphalor.was.assets.AssetsManager;
import de.siphalor.was.content.ContentManager;
import de.siphalor.was.content.lang.I18n;
import de.siphalor.was.content.product.Product;
import de.siphalor.was.content.quest.Quest;
import de.siphalor.was.content.resource.Resource;
import de.siphalor.was.game.Transaction;
import de.siphalor.was.util.Util;
import de.siphalor.was.visual.jfx.controller.BalanceController;
import de.siphalor.was.visual.jfx.controller.MainController;
import de.siphalor.was.visual.jfx.controller.OptionsController;
import de.siphalor.was.visual.jfx.controller.QuestController;
import de.siphalor.was.visual.jfx.util.JFXUtil;
import de.siphalor.was.visual.jfx.util.JFXStorageSlot;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;

public class JFXVisual extends Application implements Visual {
	private static boolean i18nInvalidated = false;
	private static Stage primaryStage;

	private static Scene gameScene;
	private static MainController gameController;
	private static GridPane storageGrid;
	private static JFXStorageSlot[][] storageSlots;

	private static Scene optionsScene;
	private static OptionsController optionsController;

	private static Stage balanceStage;
	private static BalanceController balanceController;

	private static final Map<String, Image> CACHED_IMAGES = new HashMap<>();
	@NotNull
	public static Image getProductImage(@NotNull Product product, @NotNull ContentManager contentManager) {
		return CACHED_IMAGES.computeIfAbsent(
				product.getTextureLocation(),
				loc -> loadImage(loc, contentManager).orElseGet(() -> loadImage(Product.getPlaceholderTextureLocation(), contentManager).orElseThrow())
		);
	}
	@NotNull
	public static Optional<Image> loadImage(@NotNull String location, @NotNull ContentManager contentManager) {
		Optional<Resource> optResource = contentManager.getResource(location);
		return optResource.map(resource -> {
			InputStream inputStream = resource.getInputStream();
			if (inputStream == null) return null;
			return new Image(inputStream);
		});
	}

	@Override
	public void setup(WhatAStorage whatAStorage) {
	}

	@Override
	public void run() {
		launch();
	}

	@Override
	public void onScheduleStop() {
		primaryStage.close();
		if (balanceStage != null)
			balanceStage.close();
	}

	@Override
	public void invalidateI18n() {
		i18nInvalidated = true;
	}

	@Override
	public void onBalanceChanged(int budget, Transaction transaction, int totalIncome, int totalLoss) {
		I18n i18n = I18n.getInstance();

		gameController.budgetLabel.setText(i18n.format("game.budget", budget));
		JFXUtil.setStyleClass(gameController.budgetLabel, "red", budget < 0);

		if (balanceController != null) {
			balanceController.onBalanceChanged(budget, transaction, totalIncome, totalLoss);
		}
	}

	@Override
	public void onQuestAdded(Quest newQuest, boolean canCreateMore) {
		addQuest(newQuest);

		gameController.nextQuestButton.setDisable(!canCreateMore);
	}

	@Override
	public void onQuestRemoved(int index) {
		gameController.questBox.getChildren().remove(index);

		gameController.nextQuestButton.setDisable(false);
	}

	@Override
	public void start(Stage primaryStage) throws Exception {
		JFXVisual.primaryStage = primaryStage;
		primaryStage.setTitle(WhatAStorage.TITLE);
		primaryStage.setMinWidth(850);
		primaryStage.setMinHeight(500);

		loadGameScene();
		loadOptionsScene();

		primaryStage.setOnCloseRequest(event -> {
			if (balanceStage != null)
				balanceStage.hide();
		});

		changeScene(gameScene);

		primaryStage.show();
	}

	private void changeScene(Scene nextScene) {
		primaryStage.setScene(nextScene);
	}

	public void loadGameScene() {
		gameController = new MainController(WhatAStorage.getInstance());

		gameScene = JFXUtil.loadScene("game", gameController, I18n.getInstance());

		prepareStorageGrid(3, 3, 3);

		AssetsManager.getStream("textures/bin_closed.png").ifPresent(inputStream -> {
			gameController.trash.setImage(new Image(inputStream));
		});

		onBalanceChanged(0, new Transaction(Transaction.Type.NOOP, 0, ""), 0, 0);

		gameController.balanceButton.setOnAction(event -> {
			if (balanceStage != null && balanceStage.isShowing()) {
				return;
			}

			balanceStage = new Stage();

			setupBalanceStage();

			balanceStage.show();
		});

		gameController.optionsButton.setOnAction(event -> openOptionsScene());
	}

	private void loadOptionsScene() {
		optionsController = new OptionsController();
		optionsScene = JFXUtil.loadScene("options", optionsController, I18n.getInstance());
	}

	private void setupBalanceStage() {
		if (balanceStage != null) {
			balanceController = new BalanceController();
			Scene scene = JFXUtil.loadScene("balance", balanceController, I18n.getInstance());

			balanceController.init();

			balanceStage.setOnCloseRequest(e -> {
				balanceStage = null;
				balanceController = null;
			});

			balanceStage.setScene(scene);
			balanceStage.setTitle(I18n.getInstance().getString("game.balance"));
		}
	}

	private void openOptionsScene() {
		optionsController.beforeOpen(() -> {
			if (i18nInvalidated) {
				loadGameScene();
				WhatAStorage.getInstance().resendVisualGameData();
				changeScene(gameScene);
				loadOptionsScene();
				setupBalanceStage();
				i18nInvalidated = false;
			} else {
				changeScene(gameScene);
			}
		});
		changeScene(optionsScene);
	}

	private void addQuest(Quest quest) {
		AssetsManager.getStream("jfx/quest_widget.fxml").ifPresentOrElse(is -> {
			try {
				FXMLLoader loader = new FXMLLoader();
				QuestController questController = new QuestController(quest.getType());
				loader.setController(questController);
				Parent parent = loader.load(is);

				FadeTransition transition = new FadeTransition(Duration.millis(250), parent);
				transition.setFromValue(0D);
				transition.setByValue(1D);
				transition.setInterpolator(Interpolator.EASE_OUT);

				gameController.questBox.getChildren().add(parent);
				transition.play();

				Product product = quest.getProduct();

				I18n i18n = I18n.getInstance();

				questController.title.setText(product.getName(i18n));
				questController.description.setText(product.getDescription(i18n));
				questController.reward.setText(i18n.format("game.quest.reward", quest.getReward()));

				questController.image.setImage(getProductImage(product, WhatAStorage.getInstance().getContentManager()));

				if (quest.getType() == Quest.Type.OUT) {
					parent.getStyleClass().add("quest-out");
				}

			} catch (IOException e) {
				e.printStackTrace();
			}
		}, () -> {
			Util.LOGGER.log(Level.SEVERE, "INTERNAL ERROR: Failed to load quest widget");
		});
	}

	private void prepareStorageGrid(int width, int height, int depth) {
		storageGrid = new GridPane();
		storageGrid.setId("storage-grid");
		storageGrid.setPadding(new Insets(5D));
		storageGrid.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
		AnchorPane.setTopAnchor(storageGrid, 0D);
		AnchorPane.setRightAnchor(storageGrid, 0D);
		AnchorPane.setBottomAnchor(storageGrid, 0D);
		AnchorPane.setLeftAnchor(storageGrid, 0D);

		ColumnConstraints columnConstraints = new ColumnConstraints();
		columnConstraints.setPercentWidth(100D / width);
		RowConstraints rowConstraints = new RowConstraints();
		rowConstraints.setPercentHeight(100D / height);

		storageSlots = new JFXStorageSlot[width][height];
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				JFXStorageSlot storageSlot = new JFXStorageSlot(x, y, depth);
				storageSlots[y][x] = storageSlot;
				storageGrid.add(storageSlot.getGrid(), x, y);
			}
			storageGrid.getRowConstraints().add(rowConstraints);
		}
		for (int x = 0; x < width; x++) {
			storageGrid.getColumnConstraints().add(columnConstraints);
		}

		gameController.storagePane.getChildren().add(storageGrid);
	}

	@Override
	public void onProductSet(int x, int y, int z, Product product) {
		ContentManager contentManager = WhatAStorage.getInstance().getContentManager();
		I18n i18n = I18n.getInstance();

		JFXStorageSlot slot = storageSlots[y][x];

		int depth = product.getDepth();
		int dz = z + depth - 1;

		slot.getImages()[dz].setImage(getProductImage(product, contentManager));
		slot.getImages()[dz].setTooltip(createTooltip(product, i18n));
		slot.getLabels()[dz].setText(product.getName(i18n));
		GridPane.setConstraints(slot.getImages()[dz], GridPane.getColumnIndex(slot.getImages()[dz]), dz - depth + 1, depth + 1, depth + 1);
	}

	private Tooltip createTooltip(Product product, I18n i18n) {
		Tooltip tooltip = new Tooltip(product.getName(i18n) + ": " + product.getDescription(i18n));
		tooltip.setShowDelay(Duration.millis(100));
		tooltip.setHideDelay(Duration.millis(100));
		return tooltip;
	}

	@Override
	public void onProductCleared(int x, int y, int z, Product product) {
		JFXStorageSlot slot = storageSlots[y][x];
		String empty = I18n.getInstance().getString("game.storage.empty");

		int dz = z + product.getDepth() - 1;
		slot.getImages()[dz].setImage(null);
		slot.getImages()[dz].setTooltip(null);
		slot.getLabels()[dz].setText(empty);
		GridPane.setConstraints(slot.getImages()[dz], GridPane.getColumnIndex(slot.getImages()[dz]), dz, 1, 1);
	}
}
