package com.aionemu.gameserver.services;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.model.base.*;
import com.aionemu.gameserver.model.templates.base.BaseTemplate;
import com.aionemu.gameserver.services.panesterra.PanesterraService;

/**
 * @author Source, Estrayl
 */
public class BaseService {

	private final static Logger log = LoggerFactory.getLogger(BaseService.class);
	private final static BaseService INSTANCE = new BaseService();
	private final Map<Integer, Base<?>> activeBases = new ConcurrentHashMap<>();
	private final Map<Integer, BaseLocation> allBaseLocations = new HashMap<>();

	/**
	 * Initializes all base locations
	 */
	private BaseService() {
		log.info("Initializing bases...");

		for (BaseTemplate template : DataManager.BASE_DATA.getAllBaseTemplates()) {
			BaseLocation loc = switch (template.getType()) {
				case CASUAL -> new BaseLocation(template);
				case SIEGE -> new SiegeBaseLocation(template);
				case STAINED -> new StainedBaseLocation(template);
				case PANESTERRA, PANESTERRA_ARTIFACT, PANESTERRA_FACTION_CAMP -> new PanesterraBaseLocation(template);
			};
			allBaseLocations.put(template.getId(), loc);
		}
	}

	/**
	 * Executes start of all casual and stained bases.
	 */
	public void initBases() {
		allBaseLocations.values().forEach(loc -> {
			switch (loc.getType()) {
				case CASUAL, STAINED, PANESTERRA, PANESTERRA_FACTION_CAMP -> start(loc.getId());
			}
		});
	}

	/**
	 * Generates a new BaseObject for given id
	 */
	public void start(int id) {
		final Base<?> base;

		synchronized (this) {
			if (activeBases.containsKey(id))
				return;
			base = newBase(id);
			activeBases.put(id, base);
		}
		try {
			base.start();
		} catch (BaseException | NullPointerException e) {
			log.error("Base could not be started! ID:{}", id, e);
		}
	}

	/**
	 * Removes base with given id from activeBases
	 * and stops it.
	 * Should only directly call for SiegeBases
	 */
	public void stop(int id) {
		if (!isActive(id))
			return;

		Base<?> base;
		synchronized (this) {
			base = activeBases.remove(id);
		}
		if (base == null || base.isStopped())
			return;

		try {
			base.stop();
		} catch (BaseException e) {
			log.error("Base could not be stopped! ID:{}", id, e);
		}
	}

	/**
	 * @return A type-specific base object for given id, if a base location is given for the specific id.
	 */
	private Base<?> newBase(int id) {
		BaseLocation loc = allBaseLocations.get(id);
		return switch (loc.getType()) {
			case CASUAL -> new CasualBase(loc);
			case STAINED -> new StainedBase((StainedBaseLocation) loc);
			case SIEGE -> new SiegeBase((SiegeBaseLocation) loc);
			case PANESTERRA -> new PanesterraBase((PanesterraBaseLocation) loc);
			case PANESTERRA_ARTIFACT -> new PanesterraArtifact((PanesterraBaseLocation) loc);
			case PANESTERRA_FACTION_CAMP -> new PanesterraFactionCamp((PanesterraBaseLocation) loc);
		};
	}

	public void capture(int id, BaseOccupier newOccupier) {
		if (!isActive(id))
			return;

		stop(id);

		BaseLocation loc = allBaseLocations.get(id);

		if (loc.getType() == BaseType.PANESTERRA_FACTION_CAMP)
			PanesterraService.getInstance().handleTeamElimination(loc.getOccupier().getPanesterraFaction());

		if (newOccupier != null)
			loc.setOccupier(newOccupier);

		start(id);

		if (loc instanceof StainedBaseLocation coloredLoc)
			handleStainedFeatures(coloredLoc.getColor(), newOccupier);
	}

	private void handleStainedFeatures(BaseColorType colorType, BaseOccupier newOccupier) {
		List<StainedBase> spec = activeBases.values().stream().filter(base -> base instanceof StainedBase sb && sb.getColor() == colorType)
			.map(base -> (StainedBase) base).toList();

		long equalBases = spec.stream().filter(base -> base.getOccupier() == newOccupier).count();
		if (newOccupier == BaseOccupier.BALAUR || equalBases < 3) {
			spec.forEach(StainedBase::deactivateEnhancedSpawns);
		} else {
			spec.forEach(StainedBase::scheduleEnhancedSpawns);
		}
	}

	public Map<Integer, BaseLocation> getBaseLocations() {
		return allBaseLocations;
	}

	public Base<?> getActiveBase(int id) {
		return activeBases.get(id);
	}

	public BaseLocation getBaseLocation(int id) {
		return allBaseLocations.get(id);
	}

	public boolean isActive(int id) {
		return activeBases.containsKey(id);
	}

	public static BaseService getInstance() {
		return INSTANCE;
	}
}
