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.Balance;
import de.siphalor.was.util.PersistentInputStream;
import de.siphalor.was.visual.jfx.*;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.TableColumn;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
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;

public class JFXVisual extends Application implements Visual {
	private static Stage primaryStage;
	private static final FXMLLoader loader = new FXMLLoader();

	private static Scene mainScene;

	private static MainController controller;

	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();
	}

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

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

		controller.totalIncomeLabel.setText(i18n.format("game.balance.total-income", totalIncome));
		controller.totalLossLabel.setText(i18n.format("game.balance.total-loss", totalLoss));

		ObservableList<XYChart.Data<Integer, Integer>> data = controller.budgetChartSeries.getData();
		int i = data.size();
		data.add(new XYChart.Data<>(data.size(), budget));

		if (transaction != Balance.Transaction.NOOP) {
			controller.balanceHistoryTable.getItems().add(new BalanceEntry(i, change, i18n.getString(transaction.getTranslationKey())));
			controller.balanceHistoryTable.sort();
		}
	}

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

		controller.nextQuestButton.setDisable(!canCreateMore);
	}

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

		controller.nextQuestButton.setDisable(false);
	}

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

		loader.setResources(I18n.getInstance());
		controller = new MainController(WhatAStorage.getInstance());
		loader.setController(controller);

		Pane pane = loader.load(AssetsManager.getStream("jfx/game.fxml").get());
		mainScene = new Scene(pane);
		mainScene.getStylesheets().add("assets/jfx/main.css");

		ObservableList<TableColumn<BalanceEntry, ?>> columns = controller.balanceHistoryTable.getColumns();
		columns.get(0).setCellValueFactory(new PropertyValueFactory<>("index"));
		columns.get(1).setCellValueFactory(new PropertyValueFactory<>("change"));
		columns.get(2).setCellValueFactory(new PropertyValueFactory<>("description"));

		loadMainScene();
		//noinspection unchecked
		controller.budgetChart.getData().add((XYChart.Series<NumberAxis, NumberAxis>)(Object) controller.budgetChartSeries);

		primaryStage.show();
	}

	public void loadMainScene() {
		primaryStage.setScene(mainScene);

		prepareStorageGrid();

		AssetsManager.getStream("textures/bin_closed.png").ifPresent(inputStream -> {
			controller.trash.setImage(new Image(inputStream));
		});
		controller.budgetChartSeries.setName(I18n.getInstance().getString("game.balance.chart.line"));

		onBalanceChanged(0, Balance.Transaction.NOOP, 0, 0, 0);
	}

	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);

				controller.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();
			}
		}, () -> {
			System.out.println("INTERNAL ERROR: Failed to load quest widget");
		});
	}

	private void prepareStorageGrid() {
		GridPane storageGrid = controller.storageGrid;
		storageGrid.getChildren().clear();

		PersistentInputStream inputStream = new PersistentInputStream(AssetsManager.getStream("jfx/storage_slot.fxml").orElseThrow());

		for (int y = 0; y < 3; y++) {
			for (int x = 0; x < 3; x++) {
				FXMLLoader loader = new FXMLLoader();
				StorageSlotController storageSlotController = new StorageSlotController(x, y);
				loader.setController(storageSlotController);
				loader.setResources(I18n.getInstance());

				try {
					Parent parent = loader.load(inputStream);
					inputStream.reset();

					storageGrid.add(parent, x, y);
					controller.storageSlotControllers[y][x] = storageSlotController;

					// TODO
					//storageSlotController.slot0.setImage(loadImage("textures/products/package.png", WhatAStorage.getInstance().getContentManager()).orElseThrow());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		try {
			inputStream.realClose();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

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

		StorageSlotController slot = controller.storageSlotControllers[y][x];
		switch (z) {
			default:
			case 0:
				slot.slot0.setImage(getProductImage(product, contentManager));
				slot.slot0Title.setText(product.getName(i18n));
				Tooltip.install(slot.slot0, createTooltip(product, i18n));
				break;
			case 1:
				slot.slot1.setImage(getProductImage(product, contentManager));
				slot.slot1Title.setText(product.getName(i18n));
				Tooltip.install(slot.slot1, createTooltip(product, i18n));
				break;
			case 2:
				slot.slot2.setImage(getProductImage(product, contentManager));
				slot.slot2Title.setText(product.getName(i18n));
				Tooltip.install(slot.slot2, createTooltip(product, i18n));
				break;
		}
	}

	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) {
		StorageSlotController slot = controller.storageSlotControllers[y][x];
		String empty = I18n.getInstance().getString("game.storage.empty");
		switch (z) {
			default:
			case 0:
				slot.slot0.setImage(null);
				slot.slot0Title.setText(empty);
				Tooltip.uninstall(slot.slot0, null);
				break;
			case 1:
				slot.slot1.setImage(null);
				slot.slot1Title.setText(empty);
				Tooltip.uninstall(slot.slot1, null);
				break;
			case 2:
				slot.slot2.setImage(null);
				slot.slot2Title.setText(empty);
				Tooltip.uninstall(slot.slot2, null);
				break;
		}
	}
}
