package de.siphalor.was;

import de.siphalor.was.content.ContentManager;
import de.siphalor.was.content.lang.I18n;
import de.siphalor.was.content.pack.FileContentPack;
import de.siphalor.was.content.pack.JarContentPack;
import de.siphalor.was.content.product.Product;
import de.siphalor.was.content.product.ProductManager;
import de.siphalor.was.content.quest.Quest;
import de.siphalor.was.content.quest.QuestGenerator;
import de.siphalor.was.content.quest.QuestManager;
import de.siphalor.was.game.*;
import de.siphalor.was.util.Pair;
import de.siphalor.was.util.Util;
import de.siphalor.was.visual.JFXVisual;
import de.siphalor.was.visual.Visual;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

public class WhatAStorage {
	private static final int MAX_QUESTS = 3;
	private static final int GRID_SIZE = 3;

	public static final String TITLE = "What a Storage";
	private static final WhatAStorage INSTANCE = new WhatAStorage();

	public static WhatAStorage getInstance() {
		return INSTANCE;
	}

	private final ContentManager contentManager;
	private final JarContentPack mainPack;
	private final ProductManager productManager;
	private final QuestManager questManager;

	private final Options options;

	private Visual visual;

	private boolean stopScheduled;

	private Balance balance;
	private QuestGenerator questGenerator;
	private final List<Quest> quests = new ArrayList<>(MAX_QUESTS);
	private Storage storage;

	private WhatAStorage() {
		contentManager = new ContentManager();
		mainPack = new JarContentPack("", "content");
		contentManager.addPack(mainPack);
		productManager = new ProductManager();
		questManager = new QuestManager();
		options = new Options();

		visual = new JFXVisual();
	}

	public ContentManager getContentManager() {
		return contentManager;
	}

	public ProductManager getProductManager() {
		return productManager;
	}

	public QuestManager getQuestManager() {
		return questManager;
	}

	public Options getOptions() {
		return options;
	}

	public void reload() {
		contentManager.clear();

		contentManager.addPack(mainPack);

		File[] packDirs = Path.of("packs").toFile().listFiles(File::isDirectory);
		if (packDirs != null) {
			for (File dir : packDirs) {
				contentManager.addPack(new FileContentPack(dir.getName(), dir.toPath()));
			}
		}

		options.load();
		I18n.getInstance().reload(contentManager);

		productManager.clear();
		questManager.clear();
		productManager.reload(contentManager);
		questManager.reload(contentManager, productManager);

		Util.LOGGER.log(Level.INFO, "Reloaded game content");
	}

	public void setup() {
		visual.setup(this);
		loadGame();
	}

	public void run() {
		visual.run();
	}

	public void loadGame() {
		questGenerator = questManager.get("example.test");
		//questGenerator = new RandomQuestGenerator();
		quests.clear();
		storage = new Storage(GRID_SIZE, GRID_SIZE, GRID_SIZE);
		balance = new Balance();
	}

	public void scheduleStop() {
		stopScheduled = true;
		visual.onScheduleStop();
	}

	public boolean isStopScheduled() {
		return stopScheduled;
	}

	public Balance getBalance() {
		return balance;
	}

	public String[] getLangs() {
		return contentManager.getResources("lang", "properties").map(resource -> {
			int index = resource.getId().lastIndexOf('.');
			return resource.getId().substring(Math.max(index, 0));
		}).toArray(String[]::new);
	}

	private void addTransaction(@NotNull Transaction.Type type, int change, @Nullable Product product) {
		Transaction transaction = balance.add(type, change, product);
		visual.onBalanceChanged(balance.getBudget(), transaction, balance.getTotalIncome(), balance.getTotalLoss());
	}

	public void invalidateI18n() {
		visual.invalidateI18n();
	}

	public void resendVisualGameData() {
		for (int x = 0; x < storage.getWidth(); x++) {
			for (int y = 0; y < storage.getHeight(); y++) {
				for (Pair<Integer, Product> entry : storage.get(x, y).getProductsIndexed()) {
					visual.onProductSet(x, y, entry.getFirst(), entry.getSecond());
				}
			}
		}
		for (Quest quest : quests) {
			visual.onQuestAdded(quest, quests.size() < MAX_QUESTS);
		}
		visual.onBalanceChanged(balance.getBudget(), new Transaction(Transaction.Type.NOOP, 0, ""), balance.getTotalIncome(), balance.getTotalLoss());
	}

	public boolean userRequestQuest() {
		if (quests.size() >= MAX_QUESTS) return false;

		if (!questGenerator.hasMoreElements()) {
			questGenerator.restart();
		}

		Quest next = questGenerator.nextElement();
		quests.add(next);

		visual.onQuestAdded(next, quests.size() < MAX_QUESTS);
		return true;
	}

	public void userAbandonQuest(int index) {
		if (index >= quests.size()) {
			Util.LOGGER.log(Level.SEVERE, "Attempted to abandon non-existent quest!");
			return;
		}

		Quest quest = quests.remove(index);
		addTransaction(Transaction.Type.ABANDON, -quest.getReward(), quest.getProduct());

		visual.onQuestRemoved(index);
	}

	public boolean canStoreProduct(@Nullable Product product, int x, int y) {
		return product != null && storage.get(x, y).fits(product) && product.testY(y);
	}

	public boolean canStoreProduct(int questIndex, int x, int y) {
		return canStoreProduct(quests.get(questIndex).getProduct(), x, y);
	}

	public boolean canMoveProduct(int x1, int y1, int x2, int y2) {
		return canStoreProduct(storage.get(x1, y1).front(), x2, y2);
	}

	public void userStoreProduct(int questIndex, int x, int y) {
		Quest quest = quests.get(questIndex);
		if (quest != null && quest.getType() == Quest.Type.IN) {
			Product product = quest.getProduct();

			int z = storage.get(x, y).add(product);
			if (z >= 0 && product.testY(y)) {
				quests.remove(questIndex);
				addTransaction(Transaction.Type.STORE, quest.getReward(), product);
				visual.onQuestRemoved(questIndex);
				visual.onProductSet(x, y, z, quest.getProduct());
			}
		}
	}

	public boolean canDeliverProduct(int questIndex, int x, int y) {
		Quest quest = quests.get(questIndex);
		if (quest != null && quest.getType() == Quest.Type.OUT) {
			return quest.getProduct().equals(storage.get(x, y).front());
		}
		return false;
	}

	public boolean userDeliverProduct(int questIndex, int x, int y) {
		if (canDeliverProduct(questIndex, x, y)) {
			Product product = storage.get(x, y).front();
			int z = storage.get(x, y).pop();
			addTransaction(Transaction.Type.DELIVER, quests.get(questIndex).getReward(), product);
			quests.remove(questIndex);
			visual.onQuestRemoved(questIndex);
			visual.onProductCleared(x, y, z, product);
			return true;
		}
		return false;
	}

	public boolean canQuestsResolve(int in, int out) {
		int s = quests.size();
		if (in < s && in >= 0 && out < s && out >= 0) {
			if (quests.get(in).getType() == Quest.Type.IN && quests.get(out).getType() == Quest.Type.OUT) {
				Product product = quests.get(in).getProduct();
				return product.equals(quests.get(out).getProduct());
			}
		}
		return false;
	}

	public boolean userResolveQuests(int in, int out) {
		if (options.getAllowQuestResolving()) {
			int s = quests.size();
			if (in < s && in >= 0 && out < s && out >= 0) {
				if (quests.get(in).getType() == Quest.Type.IN && quests.get(out).getType() == Quest.Type.OUT) {
					Product product = quests.get(in).getProduct();
					if (product.equals(quests.get(out).getProduct())) {
						addTransaction(Transaction.Type.STORE, quests.get(in).getReward(), product);
						addTransaction(Transaction.Type.DELIVER, quests.get(out).getReward(), product);

						if (out > in) out--;
						quests.remove(in);
						quests.remove(out);

						visual.onQuestRemoved(in);
						visual.onQuestRemoved(out);
						return true;
					}
				}
			}
		}
		return false;
	}

	public void userMoveProduct(int x1, int y1, int x2, int y2) {
		Product product = storage.get(x1, y1).front();
		if (product != null && product.testY(y2)) {
			int z2 = storage.get(x2, y2).add(product);
			if (z2 >= 0) {
				int z1 = storage.get(x1, y1).pop();

				addTransaction(Transaction.Type.MOVE, -100, product);
				visual.onProductCleared(x1, y1, z1, product);
				visual.onProductSet(x2, y2, z2, product);
			}
		}
	}

	public void userDestroyProduct(int x, int y) {
		Product product = storage.get(x, y).front();
		int z = storage.get(x, y).pop();
		if (z >= 0) {
			addTransaction(Transaction.Type.DESTROY, -500, product);
			visual.onProductCleared(x, y, z, product);
		}
	}
}
