#include "PrecompiledHeader.h"

#include "MTGDeck.h"
#include "utils.h"
#include "Subtypes.h"
#include "Translate.h"
#include "DeckMetaData.h"
#include "PriceList.h"
#include "WDataSrc.h"
#include "MTGPack.h"
#include "utils.h"
#include "DeckManager.h"
#include <iomanip>

#if defined (WIN32) || defined (LINUX)
#include <time.h>
#endif

static inline int getGrade(int v)
{
    switch (v)
    {
    case 'P':
    case 'p':
        return Constants::GRADE_SUPPORTED;
    case 'R':
    case 'r':
        return Constants::GRADE_BORDERLINE;
    case 'O':
    case 'o':
        return Constants::GRADE_UNOFFICIAL;
    case 'A':
    case 'a':
        return Constants::GRADE_CRAPPY;
    case 'S':
    case 's':
        return Constants::GRADE_UNSUPPORTED;
    case 'N':
    case 'n':
        return Constants::GRADE_DANGEROUS;
    }
    return 0;
}

//MTGAllCards
int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primitive)
{
    if ('#' == s[0]) return 1; // a comment shouldn't be treated as an error condition
    size_t i = s.find_first_of('=');
    if (i == string::npos || 0 == i)
        return 0;

    char* key = const_cast<char*> (s.c_str()); // I know what I'm doing, let me do it
    key[i] = 0;
    char* val = key + i + 1;

    switch (key[0])
    {
    case 'a':
        if (0 == strcmp("auto", key))
        {
            if (!primitive) primitive = NEW CardPrimitive();
            primitive->addMagicText(val);
        }
        else if (0 == strncmp("auto", key, 4))
        {
            if (!primitive) primitive = NEW CardPrimitive();
            primitive->addMagicText(val, key + 4);
        }
        else if (0 == strcmp("alias", key))
        {
            if (!primitive) primitive = NEW CardPrimitive();
            primitive->alias = atoi(val);
        }
        else if (0 == strcmp("abilities", key))
        {
            if (!primitive) primitive = NEW CardPrimitive();
            string value = val;
            //Specific Abilities
            std::transform(value.begin(), value.end(), value.begin(), ::tolower);
            while (value.size())
            {
                string attribute;
                size_t found2 = value.find(',');
                if (found2 != string::npos)
                {
                    attribute = value.substr(0, found2);
                    value = value.substr(found2 + 1);
                }
                else
                {
                    attribute = value;
                    value = "";
                }

                for (int j = Constants::NB_BASIC_ABILITIES - 1; j >= 0; --j)
                {
                    size_t found = attribute.find(Constants::MTGBasicAbilities[j]);
                    if (found != string::npos)
                    {
                        primitive->basicAbilities[j] = 1;
                        break;
                    }
                }
            }
        }
        break;

    case 'c': //color
        if (!primitive) primitive = NEW CardPrimitive();
        {
            string value = val;
            std::transform(value.begin(), value.end(), value.begin(), ::tolower);
            vector<string> values = split(value, ',');
            int removeAllOthers = 1;
            for (size_t values_i = 0; values_i < values.size(); ++values_i)
            {
                primitive->setColor(values[values_i], removeAllOthers);
                removeAllOthers = 0;
            }
        }
        break;

    case 'g': //grade
        if (s.size() - i - 1 > 2) currentGrade = getGrade(val[2]);
        break;

    case 'k': //kicker
        if (!primitive) primitive = NEW CardPrimitive();
        if (ManaCost * cost = primitive->getManaCost())
        {
            string value = val;
            std::transform(value.begin(), value.end(), value.begin(), ::tolower);
            cost->kicker = ManaCost::parseManaCost(value);
        }
        break;

    case 'o': //othercost
        if (!primitive) primitive = NEW CardPrimitive();
        if(key[5] == 'r')//otherrestrictions
        {
            string value = val;
            primitive->setOtherRestrictions(value);

        }
        else
        {
            if (ManaCost * cost = primitive->getManaCost())
            {
                string value = val;
                std::transform(value.begin(), value.end(), value.begin(), ::tolower);
                cost->alternative = ManaCost::parseManaCost(value);
            }
        }
        break;

    case 'b': //buyback
        if (!primitive) primitive = NEW CardPrimitive();
        if (ManaCost * cost = primitive->getManaCost())
        {
            string value = val;
            std::transform(value.begin(), value.end(), value.begin(), ::tolower);
            cost->BuyBack = ManaCost::parseManaCost(value);
        }
        break;
    case 'f': //flashback//morph
        {
            if (!primitive) primitive = NEW CardPrimitive();
            if(ManaCost * cost = primitive->getManaCost())
            {
                if( s.find("facedown") != string::npos)//morph
                {
                    string value = val;
                    std::transform(value.begin(), value.end(), value.begin(), ::tolower);
                    cost->morph = ManaCost::parseManaCost(value);
                }
                else
                {
                    string value = val;
                    std::transform(value.begin(), value.end(), value.begin(), ::tolower);
                    cost->FlashBack = ManaCost::parseManaCost(value);
                }
            }
            break;
        }
    case 'i': //id
        if (!card) card = NEW MTGCard();
        card->setMTGId(atoi(val));
        break;

    case 'm': //mana
        if (!primitive) primitive = NEW CardPrimitive();
        {
            string value = val;
            std::transform(value.begin(), value.end(), value.begin(), ::tolower);
            primitive->setManaCost(value);
        }
        break;

    case 'n': //name
        if (!primitive) primitive = NEW CardPrimitive();
        if (0 == strcmp("Bloodrock Cyclops", val)) cout << "val" << endl;
        primitive->setName(val);
        break;

    case 'p':
        if ('r' == key[1])
        { // primitive
            if (!card) card = NEW MTGCard();
            map<string, CardPrimitive*>::iterator it = primitives.find(val);
            if (it != primitives.end()) card->setPrimitive(it->second);
        }
        else
        { //power
            if (!primitive) primitive = NEW CardPrimitive();
            primitive->setPower(atoi(val));
        }
        break;

    case 'r': //retrace/rarity
        if('s' == key[2] && 't' == key[3])//restrictions
        {
            if (!primitive) primitive = NEW CardPrimitive();
            string value = val;
            primitive->setRestrictions(CardPrimitive::NO_RESTRICTION);
            primitive->hasRestriction = false;
            if (value.find("control two or more vampires") != string::npos)
                primitive->setRestrictions(CardPrimitive::VAMPIRES);
            if (value.find("control less creatures") != string::npos)
                primitive->setRestrictions(CardPrimitive::LESS_CREATURES);
            if (value.find("control snow land") != string::npos)
                primitive->setRestrictions(CardPrimitive::SNOW_LAND_INPLAY);
            if (value.find("casted a spell") != string::npos)
                primitive->setRestrictions(CardPrimitive::CASTED_A_SPELL);
            if (value.find("one of a kind") != string::npos)
                primitive->setRestrictions(CardPrimitive::ONE_OF_AKIND);
            if (value.find("fourth turn") != string::npos)
                primitive->setRestrictions(CardPrimitive::FOURTHTURN);
            if (value.find("before battle damage") != string::npos)
                primitive->setRestrictions(CardPrimitive::BEFORECOMBATDAMAGE);
            if (value.find("after battle") != string::npos)
                primitive->setRestrictions(CardPrimitive::AFTERCOMBAT);
            if (value.find("during battle") != string::npos)
                primitive->setRestrictions(CardPrimitive::DURINGCOMBAT);
            if (value.find("myturnonly") != string::npos)
                primitive->setRestrictions(CardPrimitive::PLAYER_TURN_ONLY);
            if (value.find("opponentturnonly") != string::npos)
                primitive->setRestrictions(CardPrimitive::OPPONENT_TURN_ONLY);
            if (value.find("assorcery") != string::npos)
                primitive->setRestrictions(CardPrimitive::AS_SORCERY);
            string types[] = { "my", "opponent", "" };
            int starts[] = { CardPrimitive::MY_BEFORE_BEGIN, CardPrimitive::OPPONENT_BEFORE_BEGIN, CardPrimitive::BEFORE_BEGIN };
            for (int j = 0; j < 3; ++j)
            {
                size_t found = value.find(types[j]);
                if (found != string::npos)
                {
                    for (int i = 0; i < Constants::NB_MTG_PHASES; i++)
                    {
                        string toFind = types[j];
                        toFind.append(Constants::MTGPhaseCodeNames[i]).append("only");
                        found = value.find(toFind);
                        if (found != string::npos)
                        {
                            if(primitive->hasRestriction == false)
                            {
                                primitive->setRestrictions(starts[j] + i);
                                primitive->hasRestriction = true;
                            }
                        }
                    }
                }
            }
        }
        else if ('e' == key[1] && 't' == key[2])
        { //retrace
            if (!primitive) primitive = NEW CardPrimitive();
            if (ManaCost * cost = primitive->getManaCost())
            {
                string value = val;
                std::transform(value.begin(), value.end(), value.begin(), ::tolower);
                cost->Retrace = ManaCost::parseManaCost(value);
            }
        }
        else if (s.find("rar") != string::npos)
        {//rarity
            if (!card) card = NEW MTGCard();
            card->setRarity(val[0]);
        }
        break;
    case 's': //subtype
        {
            if (s.find("suspend") != string::npos)
            {
            size_t time = s.find("suspend(");
            size_t end = s.find(")=");
            int suspendTime = atoi(s.substr(time + 8,end - 2).c_str());
                if (!primitive) primitive = NEW CardPrimitive();
                if (ManaCost * cost = primitive->getManaCost())
                {
                    string value = val;
                    std::transform(value.begin(), value.end(), value.begin(), ::tolower);
                    cost->suspend = ManaCost::parseManaCost(value);
                    primitive->suspendedTime = suspendTime;
                }
                
            }
            else
            {
                if (!primitive) primitive = NEW CardPrimitive();
                while (true)
                {
                    char* found = strchr(val, ' ');
                    if (found)
                    {
                        string value(val, found - val);
                        primitive->setSubtype(value);
                        val = found + 1;
                    }
                    else
                    {
                        primitive->setSubtype(val);
                        break;
                    }
                }
            }
            break;
        }
    case 't':
        if (!primitive) primitive = NEW CardPrimitive();
        if (0 == strcmp("target", key))
        {
            string value = val;
            std::transform(value.begin(), value.end(), value.begin(), ::tolower);
            primitive->spellTargetType = value;
        }
        else if (0 == strcmp("text", key))
            primitive->setText(val);
        else if (0 == strcmp("type", key))
        {
            while (true)
            {
                char* found = strchr(val, ' ');
                if (found)
                {
                    string value(val, found - val);
                    primitive->setType(value);
                    val = found + 1;
                }
                else
                {
                    primitive->setType(val);
                    break;
                }
            }
        }
        else if (0 == strcmp("toughness", key)) primitive->setToughness(atoi(val));
        break;

    default:
        DebugTrace( endl << "MTGDECK Parsing Error: " << " [" << primitive->getName() << "]" << s << std::endl);
        break;
    }

    tempPrimitive = primitive;
    tempCard = card;

    return i;

}

void MTGAllCards::initCounters()
{
    for (int i = 0; i < Constants::MTG_NB_COLORS; i++)
    {
        colorsCount[i] = NULL;
    }
}

void MTGAllCards::init()
{
    tempCard = NULL;
    tempPrimitive = NULL;
    total_cards = 0;
    initCounters();
}

void MTGAllCards::loadPrimitives(const char *root_directory)
{
    DIR *dir = opendir(root_directory);
    struct dirent *dit;

    while ((dit = readdir(dir)))
    {
        char fullname[1000];
        sprintf(fullname, "%s/%s", root_directory, dit->d_name);
        wagic::ifstream file(fullname);
        if (!file)
            continue;
        file.close();
        MTGCollection()->load(fullname);
    }
    closedir(dir);
}

void MTGAllCards::loadSets(const char *root_directory, const char* filename)
{
    DIR *dir = opendir(root_directory);
    struct dirent *dit;

    while ((dit = readdir(dir)))
    {
        char fullname[1000];
        sprintf(fullname, "%s/%s/%s", root_directory, dit->d_name, filename);
        wagic::ifstream file(fullname);
        if (!file)
            continue;
        file.close();
        MTGCollection()->load(fullname, dit->d_name);
    }
    closedir(dir);}

int MTGAllCards::load(const char * config_file, const char * set_name, int autoload)
{
    conf_read_mode = 0;
    const int set_id = set_name ? setlist.Add(set_name) : MTGSets::INTERNAL_SET;
    MTGSetInfo *si = setlist.getInfo(set_id);

    int lineNumber = 0;
    wagic::ifstream setFile(config_file, ios::in | ios::ate);
    if (!setFile) return total_cards;

    streampos fileSize = setFile.tellg();
    setFile.seekg(0, ios::beg);

    std::string contents;
    contents.resize((std::string::size_type) fileSize);
    setFile.read(&contents[0], fileSize);
    std::stringstream stream(contents);

    string s;

    while (true)
    {
        if (!std::getline(stream, s)) return total_cards;
        lineNumber++;
        if (!s.size()) continue;

        if (s[s.size() - 1] == '\r') s.erase(s.size() - 1); // Handle DOS files
        switch (conf_read_mode)
        {
        case MTGAllCards::READ_ANYTHING:
            if (s[0] == '[')
            {
                currentGrade = Constants::GRADE_SUPPORTED; // Default value
                conf_read_mode = ('m' == s[1]) ? MTGAllCards::READ_METADATA : MTGAllCards::READ_CARD; // M for metadata.
            }
            else
            {
                //Global grade for file, to avoid reading the entire file if unnnecessary
                if (s[0] == 'g' && s.size() > 8)
                {
                    int fileGrade = getGrade(s[8]);
                    int maxGrade = options[Options::MAX_GRADE].number;
                    if (!maxGrade) maxGrade = Constants::GRADE_BORDERLINE; //Default setting for grade is borderline?
                    if (fileGrade > maxGrade)
                    {
                        return total_cards;
                    }
                }
            }
            continue;
        case MTGAllCards::READ_METADATA:
            if (s[0] == '[' && s[1] == '/')
                conf_read_mode = MTGAllCards::READ_ANYTHING;
            else if (si) si->processConfLine(s);
            continue;
        case MTGAllCards::READ_CARD:
            if (s[0] == '[' && s[1] == '/')
            {
                conf_read_mode = MTGAllCards::READ_ANYTHING;
                if (tempPrimitive) tempPrimitive = addPrimitive(tempPrimitive, tempCard);
                if (tempCard)
                {
                    if (tempPrimitive) tempCard->setPrimitive(tempPrimitive);
                    addCardToCollection(tempCard, set_id);
                }
                tempCard = NULL;
                tempPrimitive = NULL;
            }
            else
            {
                if (!processConfLine(s, tempCard, tempPrimitive))
                    DebugTrace("MTGDECK: BAD Line: \n[" << lineNumber << "]: " << s );
            }
            continue;
        }
    }
    return total_cards;
}

MTGAllCards* MTGAllCards::instance = NULL;

MTGAllCards::MTGAllCards()
{
    init();
}

MTGAllCards::MTGAllCards(const char * config_file, const char * set_name)
{
    init();
    load(config_file, set_name, 0);
}

MTGAllCards::~MTGAllCards()
{
    for (map<int, MTGCard *>::iterator it = collection.begin(); it != collection.end(); it++)
        delete (it->second);
    collection.clear();
    ids.clear();

    for (map<string, CardPrimitive *>::iterator it = primitives.begin(); it != primitives.end(); it++)
        delete (it->second);
    primitives.clear();
}

void MTGAllCards::loadInstance()
{
    if(!instance)
        instance = new MTGAllCards();
}

void MTGAllCards::unloadAll()
{
    if(instance) {
        delete instance;
        instance = NULL;
    }
}

int MTGAllCards::randomCardId()
{
    int id = (rand() % ids.size());
    return ids[id];
}

int MTGAllCards::countBySet(int setId)
{
    int result = 0;
    map<int, MTGCard *>::iterator it;

    for (it = collection.begin(); it != collection.end(); it++)
    {
        MTGCard * c = it->second;
        if (c->setId == setId)
        {
            result++;
        }
    }
    return result;
}

//TODO more efficient way ?
int MTGAllCards::countByType(const char * _type)
{
    int result = 0;
    map<int, MTGCard *>::iterator it;
    for (it = collection.begin(); it != collection.end(); it++)
    {
        MTGCard * c = it->second;
        if (c->data->hasType(_type))
        {
            result++;
        }
    }
    return result;
}

int MTGAllCards::countByColor(int color)
{
    if (colorsCount[color] == 0)
    {
        for (int i = 0; i < Constants::MTG_NB_COLORS; i++)
        {
            colorsCount[i] = 0;
        }
        map<int, MTGCard *>::iterator it;
        for (it = collection.begin(); it != collection.end(); it++)
        {
            MTGCard * c = it->second;
            int j = c->data->getColor();

            colorsCount[j]++;
        }
    }
    return colorsCount[color];
}

int MTGAllCards::totalCards()
{
    return (total_cards);
}

bool MTGAllCards::addCardToCollection(MTGCard * card, int setId)
{
    card->setId = setId;
    int newId = card->getId();
    if (collection.find(newId) != collection.end())
    {
#if defined (_DEBUG)
        string cardName = card->data ? card->data->name : card->getImageName();
        string setName = setId != -1 ? setlist.getInfo(setId)->getName() : "";
        DebugTrace("warning, card id collision! : " << newId << " -> " << cardName << "(" << setName << ")");
#endif
        SAFE_DELETE(card);
        return false;
    }

    //Don't add cards that don't have a primitive
    if (!card->data)
    {
        SAFE_DELETE(card);
        return false;
    }
    ids.push_back(newId);

    collection[newId] = card; //Push card into collection.
    MTGSetInfo * si = setlist.getInfo(setId);
    if (si) si->count(card); //Count card in set info
    ++total_cards;
    return true;
}

CardPrimitive * MTGAllCards::addPrimitive(CardPrimitive * primitive, MTGCard * card)
{
    int maxGrade = options[Options::MAX_GRADE].number;
    if (!maxGrade) maxGrade = Constants::GRADE_BORDERLINE; //Default setting for grade is borderline?
    if (currentGrade > maxGrade)
    {
        SAFE_DELETE(primitive);
        return NULL;
    }
    string key;
    if (card)
    {
        std::stringstream ss;
        ss << card->getId();
        ss >> key;
    }
    else
        key = primitive->name;
    if (primitives.find(key) != primitives.end())
    {
        //ERROR
        //Todo move the deletion somewhere else ?
        DebugTrace("MTGDECK: primitives conflict: "<< key);
        SAFE_DELETE(primitive);
        return NULL;
    }
    //translate cards text
    Translator * t = Translator::GetInstance();
    map<string,string>::iterator it = t->tempValues.find(primitive->name);
    if (it != t->tempValues.end())
    {
        primitive->setText(it->second);
    }

    //Legacy:
    //For the Deck editor, we need Lands and Artifact to be colors...
    if (primitive->hasType(Subtypes::TYPE_LAND)) primitive->setColor(Constants::MTG_COLOR_LAND);
    if (primitive->hasType(Subtypes::TYPE_ARTIFACT)) primitive->setColor(Constants::MTG_COLOR_ARTIFACT);

    primitives[key] = primitive;
    return primitive;
}

MTGCard * MTGAllCards::getCardById(int id)
{
    map<int, MTGCard *>::iterator it = collection.find(id);
    if (it != collection.end())
    {
        return (it->second);
    }
    return 0;
}

MTGCard * MTGAllCards::_(int index)
{
    if (index >= total_cards) return NULL;
    return getCardById(ids[index]);
}

MTGCard * MTGAllCards::getCardByName(string name)
{
    if (!name.size()) return NULL;
    if (name[0] == '#') return NULL;

    map<string, MTGCard * >::iterator cached = mtgCardByNameCache.end();
    if (mtgCardByNameCache.size() > 0)
        cached = mtgCardByNameCache.find(name);
    
    if (cached!= mtgCardByNameCache.end())
    {
        return cached->second;
    }

    int cardnb = atoi(name.c_str());
    if (cardnb)
    {
        MTGCard * result = getCardById(cardnb);
        mtgCardByNameCache[name] = result;
        return result;
    }

    std::transform(name.begin(), name.end(), name.begin(), ::tolower);
    int setId = -1;
    size_t found = name.find(" (");
    if (found != string::npos)
    {
        size_t end = name.find(")");
        string setName = name.substr(found + 2, end - found - 2);
        trim(setName);
        name = name.substr(0, found);
        trim(name);
        setId = setlist[setName];
    }
    map<int, MTGCard *>::iterator it;
    for (it = collection.begin(); it != collection.end(); it++)
    {
        MTGCard * c = it->second;
        if (setId != -1 && setId != c->setId) continue;
        string cardName = c->data->name;
        std::transform(cardName.begin(), cardName.end(), cardName.begin(), ::tolower);
        if (cardName.compare(name) == 0) {
            mtgCardByNameCache[name] = c;
            return c;
        }

    }
    mtgCardByNameCache[name] = NULL;
    return NULL;
}

//MTGDeck
MTGDeck::MTGDeck(MTGAllCards * _allcards)
{
    total_cards = 0;
    database = _allcards;
    filename = "";
    meta_name = "";
}

int MTGDeck::totalPrice()
{
    int total = 0;
    PriceList * pricelist = NEW PriceList(JGE_GET_RES("settings/prices.dat").c_str(), MTGCollection());
    map<int, int>::iterator it;
    for (it = cards.begin(); it != cards.end(); it++)
    {
        int nb = it->second;
        if (nb) total += pricelist->getPrice(it->first);
    }
    SAFE_DELETE(pricelist);
    return total;
}

MTGDeck::MTGDeck(const char * config_file, MTGAllCards * _allcards, int meta_only,int difficultyRating)
{
    total_cards = 0;
    database = _allcards;
    filename = config_file;
    size_t slash = filename.find_last_of("/");
    size_t dot = filename.find(".");
    meta_name = filename.substr(slash + 1, dot - slash - 1);
    meta_id = atoi(meta_name.substr(4).c_str());
    wagic::ifstream file(config_file);
    std::string s;
    
    if (file)
    {
        while (std::getline(file, s))
        {
            if (!s.size()) continue;
            if (s[s.size() - 1] == '\r') s.erase(s.size() - 1); //Handle DOS files
            if (s[0] == '#')
            {
                size_t found = s.find("NAME:");
                if (found != string::npos)
                {
                    meta_name = s.substr(found + 5);
                    continue;
                }
                found = s.find("DESC:");
                if (found != string::npos)
                {
                    if (meta_desc.size()) meta_desc.append("\n");
                    meta_desc.append(s.substr(found + 5));
                    continue;
                }
                continue;
            }
            if (meta_only) break;
            int numberOfCopies = 1;
            size_t found = s.find(" *");
            if (found != string::npos)
            {
                numberOfCopies = atoi(s.substr(found + 2).c_str());
                s = s.substr(0, found);
            }
            size_t diff = s.find("toggledifficulty:");
            if(diff != string::npos)
            {
                string cards = s.substr(diff + 17);
                size_t separator = cards.find("|");
                string cardeasy = cards.substr(0,separator);
                string cardhard = cards.substr(separator + 1);
                if(difficultyRating == HARD)
                {
                    s = cardhard;
                }
                else
                {
                    s = cardeasy;
                }
            }
            MTGCard * card = database->getCardByName(s);
            if (card)
            {
                for (int i = 0; i < numberOfCopies; i++)
                {
                    add(card);
                }
            }
            else
            {
                DebugTrace("could not find Card matching name: " << s);
            }
        }
        file.close();
    }
    else
    {
        //TODO Error management
    }
}

int MTGDeck::totalCards()
{
    return total_cards;
}

string MTGDeck::getFilename()
{
    return filename;
}

MTGCard * MTGDeck::getCardById(int mtgId)
{
    return database->getCardById(mtgId);
}

int MTGDeck::addRandomCards(int howmany, int * setIds, int nbSets, int rarity, const char * _subtype, int * colors, int nbcolors)
{
    if (howmany <= 0) return 1;

    int unallowedColors[Constants::MTG_NB_COLORS + 1];
    for (int i = 0; i < Constants::MTG_NB_COLORS; ++i)
    {
        if (nbcolors)
            unallowedColors[i] = 1;
        else
            unallowedColors[i] = 0;
    }
    for (int i = 0; i < nbcolors; ++i)
    {
        unallowedColors[colors[i]] = 0;
    }

    int collectionTotal = database->totalCards();
    if (!collectionTotal) return 0;

    char subtype[4096];
    if (_subtype) sprintf(subtype, "%s", _subtype);

    vector<int> subcollection;
    int subtotal = 0;
    for (int i = 0; i < collectionTotal; i++)
    {
        MTGCard * card = database->_(i);
        int r = card->getRarity();
        if (r != Constants::RARITY_T && (rarity == -1 || r == rarity) && // remove tokens
            card->setId != MTGSets::INTERNAL_SET && //remove cards that are defined in primitives. Those are workarounds (usually tokens) and should only be used internally
            (!_subtype || card->data->hasSubtype(subtype)))
        {
            int ok = 0;

            if (!nbSets) ok = 1;
            for (int j = 0; j < nbSets; ++j)
            {
                if (card->setId == setIds[j])
                {
                    ok = 1;
                    break;
                }
            }

            if (ok)
            {
                for (int j = 0; j < Constants::MTG_NB_COLORS; ++j)
                {
                    if (unallowedColors[j] && card->data->hasColor(j))
                    {
                        ok = 0;
                        break;
                    }
                }
            }

            if (ok)
            {
                subcollection.push_back(card->getId());
                subtotal++;
            }
        }
    }
    if (subtotal == 0)
    {
        if (rarity == Constants::RARITY_M) return addRandomCards(howmany, setIds, nbSets, Constants::RARITY_R, _subtype, colors, nbcolors);
        return 0;
    }
    for (int i = 0; i < howmany; i++)
    {
        int id = (rand() % subtotal);
        add(subcollection[id]);
    }
    return 1;
}

int MTGDeck::add(MTGDeck * deck)
{
    map<int, int>::iterator it;
    for (it = deck->cards.begin(); it != deck->cards.end(); it++)
    {
        for (int i = 0; i < it->second; i++)
        {
            add(it->first);
        }
    }
    return deck->totalCards();
}

int MTGDeck::add(int cardid)
{
    if (!database->getCardById(cardid)) return 0;
    if (cards.find(cardid) == cards.end())
    {
        cards[cardid] = 1;
    }
    else
    {
        cards[cardid]++;
    }
    ++total_cards;
    //initCounters();
    return total_cards;
}

int MTGDeck::add(MTGCard * card)
{
    if (!card) return 0;
    return (add(card->getId()));
}

int MTGDeck::complete()
{
    /* (PSY) adds cards to the deck/collection. Makes sure that the deck
    or collection has at least 4 of every implemented card. Does not
    change the number of cards of which already 4 or more are present. */
    int id, n;
    bool StypeIsNothing;
    size_t databaseSize = database->ids.size();
    for (size_t it = 0; it < databaseSize; it++)
    {
        id = database->ids[it];
        StypeIsNothing = false;
        if (database->getCardById(id)->data->hasType("nothing"))
        {
            StypeIsNothing = true;
        }
        if (!StypeIsNothing )
        {
            if (cards.find(id) == cards.end())
            {
                cards[id] = 4;
                total_cards += 4;
            }
            else
            {
                n = cards[id];
                if (n < 4)
                {
                    total_cards += 4 - n;
                    cards[id] = 4;
                }
            }
        }
    }
    return 1;
}

int MTGDeck::removeAll()
{
    total_cards = 0;
    cards.clear();
    //initCounters();
    return 1;
}

int MTGDeck::remove(int cardid)
{
    if (cards.find(cardid) == cards.end() || cards[cardid] == 0) return 0;
    cards[cardid]--;
    total_cards--;
    //initCounters();
    return 1;
}

int MTGDeck::remove(MTGCard * card)
{
    if (!card) return 0;
    return (remove(card->getId()));
}

int MTGDeck::save()
{
    return save(filename, false, meta_name, meta_desc);
}

int MTGDeck::save(const string& destFileName, bool useExpandedDescriptions, const string& deckTitle, const string& deckDesc)
{
    string tmp = destFileName;
    tmp.append(".tmp"); //not thread safe
    std::ofstream file(tmp.c_str());
    char writer[512];
    if (file)
    {
        DebugTrace("Saving Deck: " << deckTitle << " to " << destFileName );
        if (meta_name.size())
        {
            file << "#NAME:" << deckTitle << '\n';
        }

        if (meta_desc.size())
        {
            size_t found = 0;
            string desc = deckDesc;
            found = desc.find_first_of("\n");
            while (found != string::npos)
            {
                file << "#DESC:" << desc.substr(0, found + 1);
                desc = desc.substr(found + 1);
                found = desc.find_first_of("\n");
            }
            file << "#DESC:" << desc << "\n";
        }

        bool saveDetailedDeckInfo = options.get( Options::SAVEDETAILEDDECKINFO )->number == 1;

        if ( filename.find("collection.dat") != string::npos )
            saveDetailedDeckInfo = false;

        if (useExpandedDescriptions || saveDetailedDeckInfo)
        {
            printDetailedDeckText(file);
        }
        else
        {
            map<int, int>::iterator it;
            for (it = cards.begin(); it != cards.end(); it++)
            {
                sprintf(writer, "%i\n", it->first);
                for (int j = 0; j < it->second; j++)
                {
                    file << writer;
                }
            }
        }
        file.close();
        std::remove(destFileName.c_str());
        rename(tmp.c_str(), destFileName.c_str());
    }
    return 1;
}

/***
    print out an expanded version of the deck to file.  
    This save meta data about each card to allow easy reading of the deck file.  It will
    also save each card by id, to speed up the loading of the deck next time.
*/
void MTGDeck::printDetailedDeckText(std::ofstream& file )
{
    ostringstream currentCard, creatures, lands, spells, types;
    map<int, int>::iterator it;
    for (it = cards.begin(); it != cards.end(); it++)
    {
        int cardId = it->first;
        int nbCards = it->second;
        MTGCard *card = this->getCardById( cardId );
        if (card == NULL)
        {
            continue;
        }
        MTGSetInfo *setInfo = setlist.getInfo(card->setId);
        string setName = setInfo->id;
        string cardName = card->data->getName();
        string description = card->data->getText();

        currentCard << "#" << nbCards << " x " << cardName << " (" << setName << "), ";

        if ( !card->data->isLand() )
            currentCard << card->data->getManaCost() << ", ";

        // Add the card's types 
        vector<int>::iterator typeIter;
        for ( typeIter = card->data->types.begin(); typeIter != card->data->types.end(); ++typeIter )
            types << Subtypes::subtypesList->find( *typeIter ) << " ";

        currentCard << trim(types.str()) << ", ";
        types.str(""); // reset the buffer.

        // Add P/T if a creature
        if ( card->data->isCreature() )
            currentCard << card->data->getPower() << "/" << card->data->getToughness() << ", ";


        if ( card->data->hasRestriction )
            currentCard << ", " << card->data->otherrestriction;

        map<int,int>::iterator abilityIter;
        for ( abilityIter = card->data->basicAbilities.begin(); abilityIter != card->data->basicAbilities.end(); ++abilityIter )
            currentCard << Constants::MTGBasicAbilities[ abilityIter->first ] << "; ";
        currentCard <<endl;

        for ( int i = 0; i < nbCards; i++ )
            currentCard << cardId << endl;

        currentCard <<endl;
        setInfo = NULL;
        if ( card->data->isLand() )
            lands<< currentCard.str();
        else if ( card->data->isCreature() )
            creatures << currentCard.str();
        else 
            spells << currentCard.str();
        currentCard.str("");
    }
    file << getCardBlockText( "Creatures", creatures.str() ) << endl;
    file << getCardBlockText( "Spells", spells.str() ) << endl;
    file << getCardBlockText( "Lands", lands.str() ) << endl;
    creatures.str("");
    spells.str("");
    lands.str("");
}

/***
* Convience method to print out blocks of card descriptions
*/
string MTGDeck::getCardBlockText( const string& title, const string& text )
{
    ostringstream oss;
    string textBlock (text);

    oss << setfill('#') << setw( 40 ) << "#" << endl;
    oss << "#    " << setfill(' ') << setw(34) << left << title << "#" << endl;
    oss << setfill('#') << setw( 40 ) << "#" << endl;
    oss << trim(textBlock) << endl;

    return oss.str();    
}

//MTGSets
MTGSets setlist; //Our global.

MTGSets::MTGSets()
{
}

MTGSets::~MTGSets()
{
    for (size_t i = 0; i < setinfo.size(); ++i)
    {
        delete (setinfo[i]);
    }
}

MTGSetInfo* MTGSets::getInfo(int setID)
{
    if (setID < 0 || setID >= (int) setinfo.size()) return NULL;

    return setinfo[setID];
}

MTGSetInfo* MTGSets::randomSet(int blockId, int atleast)
{
    char * unlocked = (char *) calloc(size(), sizeof(char));

    //Figure out which sets are available.
    for (int i = 0; i < size(); i++)
    {
        unlocked[i] = options[Options::optionSet(i)].number;
    }
    //No luck randomly. Now iterate from a random location.
    int a = 0, iter = 0;
    while (iter < 3)
    {
        a = rand() % size();
        for (int i = a; i < size(); i++)
        {
            if (unlocked[i] && (blockId == -1 || setinfo[i]->block == blockId) &&
                (atleast == -1 || setinfo[i]->totalCards() >= atleast))
            {
                free(unlocked);
                return setinfo[i];
            }
        }
        for (int i = 0; i < a; i++)
        {
            if (unlocked[i] && (blockId == -1 || setinfo[i]->block == blockId) &&
                (atleast == -1 || setinfo[i]->totalCards() >= atleast))
            {
                free(unlocked);
                return setinfo[i];
            }
        }
        blockId = -1;
        iter++;
        if (iter == 2) atleast = -1;
    }
    free(unlocked);
    return NULL;
}

int blockSize(int blockId);

int MTGSets::Add(const char * name)
{
    int setid = findSet(name);
    if (setid != -1) return setid;

    MTGSetInfo* s = NEW MTGSetInfo(name);
    setinfo.push_back(s);
    setid = (int) setinfo.size();

    return setid - 1;
}

int MTGSets::findSet(string name)
{
    std::transform(name.begin(), name.end(), name.begin(), ::tolower);

    for (int i = 0; i < (int) setinfo.size(); i++)
    {
        MTGSetInfo* s = setinfo[i];
        if (!s) continue;
        string set = s->id;
        std::transform(set.begin(), set.end(), set.begin(), ::tolower);
        if (set.compare(name) == 0) return i;
    }
    return -1;
}

int MTGSets::findBlock(string s)
{
    if (!s.size()) return -1;

    string comp = s;
    std::transform(comp.begin(), comp.end(), comp.begin(), ::tolower);
    for (int i = 0; i < (int) blocks.size(); i++)
    {
        string b = blocks[i];
        std::transform(b.begin(), b.end(), b.begin(), ::tolower);
        if (b.compare(comp) == 0) return i;
    }

    blocks.push_back(s);
    return ((int) blocks.size()) - 1;
}

int MTGSets::operator[](string id)
{
    return findSet(id);
}

string MTGSets::operator[](int id)
{
    if (id < 0 || id >= (int) setinfo.size()) return "";

    MTGSetInfo * si = setinfo[id];
    if (!si) return "";

    return si->id;
}

int MTGSets::getSetNum(MTGSetInfo*i)
{
    int it;
    for (it = 0; it < size(); it++)
    {
        if (setinfo[it] == i) return it;
    }
    return -1;
}
int MTGSets::size()
{
    return (int) setinfo.size();
}

//MTGSetInfo
MTGSetInfo::~MTGSetInfo()
{
    SAFE_DELETE(mPack);
}

MTGSetInfo::MTGSetInfo(string _id)
{
    string whitespaces(" \t\f\v\n\r");
    id = _id;
    block = -1;
    year = -1;

    for (int i = 0; i < MTGSetInfo::MAX_COUNT; i++)
        counts[i] = 0;

    char myFilename[4096];
    sprintf(myFilename, JGE_GET_RES("sets/%s/booster.txt").c_str(), id.c_str());
    mPack = NEW MTGPack(myFilename);
    if (!mPack->isValid())
    {
        SAFE_DELETE(mPack);
    }
    bZipped = false;
    bThemeZipped = false;
}

void MTGSetInfo::count(MTGCard*c)
{
    if (!c) return;

    switch (c->getRarity())
    {
    case Constants::RARITY_M:
        counts[MTGSetInfo::MYTHIC]++;
        break;
    case Constants::RARITY_R:
        counts[MTGSetInfo::RARE]++;
        break;
    case Constants::RARITY_U:
        counts[MTGSetInfo::UNCOMMON]++;
        break;
    case Constants::RARITY_C:
        counts[MTGSetInfo::COMMON]++;
        break;
    default:
    case Constants::RARITY_L:
        counts[MTGSetInfo::LAND]++;
        break;
    }

    counts[MTGSetInfo::TOTAL_CARDS]++;
}

int MTGSetInfo::totalCards()
{
    return counts[MTGSetInfo::TOTAL_CARDS];
}

string MTGSetInfo::getName()
{
    if (name.size()) return name; //Pretty name is translated when rendering.
    return id; //Ugly name as well.
}

string MTGSetInfo::getBlock()
{
    if (block < 0 || block >= (int) setlist.blocks.size()) return "None";

    return setlist.blocks[block];
}

void MTGSetInfo::processConfLine(string line)
{
    size_t i = line.find_first_of("=");
    if (i == string::npos) return;

    string key = line.substr(0, i);
    std::transform(key.begin(), key.end(), key.begin(), ::tolower);
    string value = line.substr(i + 1);

    if (key.compare("name") == 0)
        name = value;
    else if (key.compare("author") == 0)
        author = value;
    else if (key.compare("block") == 0)
        block = setlist.findBlock(value.c_str());
    else if (key.compare("year") == 0) year = atoi(value.c_str());
}
