package emu.grasscutter.game.quest;

import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.MainQuestData;
import emu.grasscutter.data.binout.MainQuestData.SubQuestData;
import emu.grasscutter.data.excels.ChapterData;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.data.excels.QuestData.QuestCondition;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.proto.ChapterStateOuterClass;
import emu.grasscutter.net.proto.QuestOuterClass.Quest;
import emu.grasscutter.server.packet.send.PacketChapterStateNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.utils.Utils;

@Entity
public class GameQuest {
	@Transient private GameMainQuest mainQuest;
	@Transient private QuestData questData;

	private int questId;
	private int mainQuestId;
	private QuestState state;

	private int startTime;
	private int acceptTime;
	private int finishTime;

	private int[] finishProgressList;
	private int[] failProgressList;

	@Deprecated // Morphia only. Do not use.
	public GameQuest() {}

	public GameQuest(GameMainQuest mainQuest, QuestData questData) {
		this.mainQuest = mainQuest;
		this.questId = questData.getId();
		this.mainQuestId = questData.getMainId();
		this.questData = questData;
		this.acceptTime = Utils.getCurrentSeconds();
		this.startTime = this.acceptTime;
		this.state = QuestState.QUEST_STATE_UNFINISHED;

		if (questData.getFinishCond() != null && questData.getAcceptCond().size() != 0) {
			this.finishProgressList = new int[questData.getFinishCond().size()];
		}

		if (questData.getFailCond() != null && questData.getFailCond().size() != 0) {
			this.failProgressList = new int[questData.getFailCond().size()];
		}

		this.mainQuest.getChildQuests().put(this.questId, this);

        this.getData().getBeginExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam()));

        this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.questId, this.state.getValue());

        if (ChapterData.beginQuestChapterMap.containsKey(questId)){
            mainQuest.getOwner().sendPacket(new PacketChapterStateNotify(
                ChapterData.beginQuestChapterMap.get(questId).getId(),
                ChapterStateOuterClass.ChapterState.CHAPTER_STATE_BEGIN
            ));
        }

        Grasscutter.getLogger().debug("Quest {} is started", questId);
	}

	public GameMainQuest getMainQuest() {
		return mainQuest;
	}

	public void setMainQuest(GameMainQuest mainQuest) {
		this.mainQuest = mainQuest;
	}

	public Player getOwner() {
		return getMainQuest().getOwner();
	}

	public int getQuestId() {
		return questId;
	}

	public int getMainQuestId() {
		return mainQuestId;
	}

	public QuestData getData() {
		return questData;
	}

	public void setConfig(QuestData config) {
		if (this.getQuestId() != config.getId()) return;
		this.questData = config;
	}

	public QuestState getState() {
		return state;
	}

	public void setState(QuestState state) {
		this.state = state;
	}

	public int getStartTime() {
		return startTime;
	}

	public void setStartTime(int startTime) {
		this.startTime = startTime;
	}

	public int getAcceptTime() {
		return acceptTime;
	}

	public void setAcceptTime(int acceptTime) {
		this.acceptTime = acceptTime;
	}

	public int getFinishTime() {
		return finishTime;
	}

	public void setFinishTime(int finishTime) {
		this.finishTime = finishTime;
	}

	public int[] getFinishProgressList() {
		return finishProgressList;
	}

	public void setFinishProgress(int index, int value) {
		finishProgressList[index] = value;
	}

	public int[] getFailProgressList() {
		return failProgressList;
	}

	public void setFailProgress(int index, int value) {
		failProgressList[index] = value;
	}

	public void finish() {
		this.state = QuestState.QUEST_STATE_FINISHED;
		this.finishTime = Utils.getCurrentSeconds();

		if (this.getFinishProgressList() != null) {
			for (int i = 0 ; i < getFinishProgressList().length; i++) {
				getFinishProgressList()[i] = 1;
			}
		}

		this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this));
		this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this));

		if (this.getData().finishParent()) {
			// This quest finishes the questline - the main quest will also save the quest to db so we dont have to call save() here
			this.getMainQuest().finish();
		} else {
			// Try and accept other quests if possible
			this.tryAcceptQuestLine();
			this.save();
		}

        this.getData().getFinishExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam()));

        this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.questId, this.state.getValue());

        if (ChapterData.endQuestChapterMap.containsKey(questId)){
            mainQuest.getOwner().sendPacket(new PacketChapterStateNotify(
                ChapterData.endQuestChapterMap.get(questId).getId(),
                ChapterStateOuterClass.ChapterState.CHAPTER_STATE_END
            ));
        }

        Grasscutter.getLogger().debug("Quest {} is finished", questId);
	}

	public boolean tryAcceptQuestLine() {
		try {
			MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId());

			for (SubQuestData subQuest : questConfig.getSubQuests()) {
				GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId());

				if (quest == null) {
					QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId());

					if (questData == null || questData.getAcceptCond() == null
							|| questData.getAcceptCond().size() == 0) {
						continue;
					}

					int[] accept = new int[questData.getAcceptCond().size()];

					// TODO
					for (int i = 0; i < questData.getAcceptCond().size(); i++) {
						QuestCondition condition = questData.getAcceptCond().get(i);
						boolean result = getOwner().getServer().getQuestSystem().triggerCondition(this, condition,
                                condition.getParamStr(),
								condition.getParam());

						accept[i] = result ? 1 : 0;
					}

					boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept);

					if (shouldAccept) {
						this.getOwner().getQuestManager().addQuest(questData.getId());
					}
				}
			}
		} catch (Exception e) {
			Grasscutter.getLogger().error("An error occurred while trying to accept quest.", e);
		}

		return false;
	}

	public void save() {
		getMainQuest().save();
	}

	public Quest toProto() {
		Quest.Builder proto = Quest.newBuilder()
				.setQuestId(this.getQuestId())
				.setState(this.getState().getValue())
				.setParentQuestId(this.getMainQuestId())
				.setStartTime(this.getStartTime())
				.setStartGameTime(438)
				.setAcceptTime(this.getAcceptTime());

		if (this.getFinishProgressList() != null) {
			for (int i : this.getFinishProgressList()) {
				proto.addFinishProgressList(i);
			}
		}

		if (this.getFailProgressList() != null) {
			for (int i : this.getFailProgressList()) {
				proto.addFailProgressList(i);
			}
		}

		return proto.build();
	}
}