package net.demilich.metastone.game.cards;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;

import org.jsoup.Jsoup;
import org.jsoup.Connection.Response;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.demilich.metastone.BuildConfig;
import net.demilich.metastone.game.Attribute;
import net.demilich.metastone.game.cards.desc.CardDesc;
import net.demilich.metastone.game.decks.DeckFormat;
import net.demilich.metastone.game.entities.heroes.HeroClass;
import net.demilich.metastone.utils.MetastoneProperties;
import net.demilich.metastone.utils.ResourceInputStream;
import net.demilich.metastone.utils.ResourceLoader;
import net.demilich.metastone.utils.UserHomeMetastone;

public class CardCatalogue {

	public static final String CARDS_FOLDER = "cards";
	public static final String LOCAL_CARDS_FOLDER = "../cards/src/main/resources/cards/";
	public static final String CARDS_FOLDER_PATH = UserHomeMetastone.getPath() + File.separator + CARDS_FOLDER;
	public static final String CARDS_COPIED_PROPERTY = "cardRevision";

	private static Logger logger = LoggerFactory.getLogger(CardCatalogue.class);

	private final static CardCollection cards = new CardCollection();

	public static void add(Card card) {
		cards.add(card);
	}

	public static CardCollection getAll() {
		CardCollection result = new CardCollection();
		for (Card card : cards) {
			result.add(card);
		}
		return result;
	}

	public static Card getCardById(String id) {
		for (Card card : cards) {
			if (card.getCardId() != null && card.getCardId().equalsIgnoreCase(id)) {
				return card.clone();
			}
		}

		return null;
	}

	public static Card getCardByName(String name) {
		for (Card card : cards) {
			if (card.isCollectible() && card.getName().equals(name)) {
				return card.clone();
			}
		}

		return null;
	}
	
	public static Card getCardByBlizzardId(String id) throws IOException{
		Response response= Jsoup.connect("http://metastats.net/cardstats/" + id.toUpperCase() + "/")
		           .ignoreContentType(true)
		           .userAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0")  
		           .referrer("http://www.google.com")   
		           .timeout(12000) 
		           .followRedirects(true)
		           .execute();
		Document doc = response.parse();
		return getCardByName(doc.getElementsByTag("h3").get(0).text());
	}

	public static CardCollection getHeroes() {
		return query(null, card -> card.isCollectible() && card.getCardType() == CardType.HERO);
	}

	public static CardCollection getHeroPowers(DeckFormat deckFormat) {
		return query(deckFormat, card -> card.isCollectible() && card.getCardType() == CardType.HERO_POWER);
	}

	public static CardCollection query(DeckFormat deckFormat) {
		return query(deckFormat, (CardType) null, (Rarity) null, (HeroClass) null, (Attribute) null);
	}

	public static CardCollection query(DeckFormat deckFormat, CardType cardType) {
		return query(deckFormat, cardType, (Rarity) null, (HeroClass) null, (Attribute) null);
	}

	public static CardCollection query(DeckFormat deckFormat, HeroClass heroClass) {
		return query(deckFormat, (CardType) null, (Rarity) null, heroClass, (Attribute) null);
	}

	public static CardCollection query(DeckFormat deckFormat, CardType cardType, Rarity rarity, HeroClass heroClass) {
		return query(deckFormat, cardType, rarity, heroClass, (Attribute) null);
	}

	public static CardCollection query(DeckFormat deckFormat, CardType cardType, Rarity rarity, HeroClass heroClass, Attribute tag) {
		CardCollection result = new CardCollection();
		for (Card card : cards) {
			if (!deckFormat.isInFormat(card)) {
				continue;
			}
			if (!card.isCollectible()) {
				continue;
			}
			if (cardType != null && !card.getCardType().isCardType(cardType)) {
				continue;
			}
			// per default, do not include heroes or hero powers
			if (card.getCardType().isCardType(CardType.HERO_POWER) || card.getCardType().isCardType(CardType.HERO)) {
				continue;
			}
			if (rarity != null && !card.getRarity().isRarity(rarity)) {
				continue;
			}
			if (heroClass != null && !card.hasHeroClass(heroClass)) {
				continue;
			}
			if (tag != null && !card.hasAttribute(tag)) {
				continue;
			}
			result.add(card.clone());
		}

		return result;
	}

	public static CardCollection query(DeckFormat deckFormat, Predicate<Card> filter) {
		CardCollection result = new CardCollection();
		for (Card card : cards) {
			if (deckFormat != null && !deckFormat.isInFormat(card)) {
				continue;
			}
			if (filter.test(card)) {
				result.add(card);
			}
		}
		return result;
	}
	
	public static void loadLocalCards() throws IOException, URISyntaxException, CardParseException {
		// load cards from ~/metastone/cards on the file system
		Collection<ResourceInputStream> inputStreams = ResourceLoader.loadJsonInputStreams(CARDS_FOLDER, false);
		loadCards(inputStreams);
	}
	
	public static void loadCards() throws IOException, URISyntaxException, CardParseException {
		// load cards from ~/metastone/cards on the file system
		Collection<ResourceInputStream> inputStreams = ResourceLoader.loadJsonInputStreams(CARDS_FOLDER_PATH, true);
		loadCards(inputStreams);
	}

	
	private static void loadCards(Collection<ResourceInputStream> inputStreams) throws IOException, URISyntaxException, CardParseException {
		Map<String, CardDesc> cardDesc = new HashMap<String, CardDesc>();
		ArrayList<String> badCards = new ArrayList<>();
		CardParser cardParser = new CardParser();
		for (ResourceInputStream resourceInputStream : inputStreams) {
			try {
				CardDesc desc = cardParser.parseCard(resourceInputStream);
				if (cardDesc.containsKey(desc.id)) {
					logger.error("Card id {} is duplicated!", desc.id);
				}
				cardDesc.put(desc.id, desc);
			} catch (Exception e) {
				//logger.error("Error parsing card '{}'", resourceInputStream.fileName);
				logger.error(e.toString());
				badCards.add(resourceInputStream.fileName);
			}
		}

		for (CardDesc desc : cardDesc.values()) {
			Card instance = desc.createInstance();
			CardCatalogue.add(instance);
			logger.debug("Adding {} to CardCatalogue", instance);
		}
		
		if (!badCards.isEmpty()) {
			throw new CardParseException(badCards);
		}
	}

	public static void copyCardsFromResources() throws IOException, URISyntaxException {
		// if we have not copied cards to the USER_HOME_METASTONE cards folder,
		// then do so now
		int cardRevision = MetastoneProperties.getInt(CARDS_COPIED_PROPERTY, 0);
		System.out.println("Existing card revision = " + cardRevision);
		if (BuildConfig.CARD_REVISION > cardRevision) {
			logger.info("Card update required: MetaStone card revision is: {}, last card update was with revision {}", BuildConfig.CARD_REVISION, cardRevision);
			ResourceLoader.copyFromResources(CARDS_FOLDER, CARDS_FOLDER_PATH);

			// set a property to indicate that we have copied the cards with current version
			MetastoneProperties.setProperty(CARDS_COPIED_PROPERTY, String.valueOf(BuildConfig.CARD_REVISION));
		} else {
			logger.info("Cards in user home folder are up-to-date: rev {}", cardRevision);
		}
	}
}