#include "PrecompiledHeader.h"

#include "TargetChooser.h"
#include "CardDescriptor.h"
#include "MTGGameZones.h"
#include "GameObserver.h"
#include "Subtypes.h"
#include "Counters.h"
#include "WEvent.h"
#include "AllAbilities.h"

TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInstance * card, MTGAbility * ability)
{
    if (!s.size()) return NULL;

    int zones[10];
    int nbzones = 0;
    size_t found;
    bool other = false;

    found = s.find("mytgt");
    if (found == 0)
    {
        MTGCardInstance * target = card->target;
        if (ability) target = (MTGCardInstance *) (ability->target);
        return NEW CardTargetChooser(target, card);
    };

    found = s.find("opponent");
    if (found == 0)
    {
        int maxtargets = 1;
        Player * opponent = card->controller()->opponent();
        return NEW PlayerTargetChooser(card, maxtargets, opponent);
    };

    found = s.find("controller");
    if (found == 0)
    {
        int maxtargets = 1;
        Player * controller = card->controller();
        return NEW PlayerTargetChooser(card, maxtargets, controller);
    };

    found = s.find("other ");
    if (found == 0)
    {
        other = true;
        s = s.substr(6);
    }

    found = s.find("trigger");
    if (found == 0)
    {
        if (s.length() > 7 && s.find("[") == 7)
        {
            string s1 = s.substr(7, s.find("]"));
            if (s1.find("to") != string::npos) return NEW TriggerTargetChooser(WEvent::TARGET_TO);
            if (s1.find("from") != string::npos) return NEW TriggerTargetChooser(WEvent::TARGET_FROM);
        }
        return NEW TriggerTargetChooser(1);
    }

    found = s.find("player");
    if (found != string::npos)
    {
        int maxtargets = 1;
        size_t several = s.find_first_of('s', 5);
        if (several != string::npos) maxtargets = -1;
        found = s.find("creature");
        if (found != string::npos) return NEW DamageableTargetChooser(card, maxtargets, other); //Any Damageable target (player, creature)
        return NEW PlayerTargetChooser(card, maxtargets); //Any player
    }

    string s1;
    found = s.find("|");
    if (found != string::npos)
    {
        string s2;
        s1 = s.substr(0, found);
        s2 = s.substr(found + 1);
        while (s2.size())
        {
            found = s2.find(",");
            string zoneName;
            if (found != string::npos)
            {
                zoneName = s2.substr(0, found);
                s2 = s2.substr(found + 1);
            }
            else
            {
                zoneName = s2;
                s2 = "";
            }
            zones[nbzones] = MTGGameZone::MY_BATTLEFIELD;

            if (zoneName.compare("*") == 0)
            {
                zones[nbzones++] = MTGGameZone::ALL_ZONES;
            }
            else if (zoneName.compare("graveyard") == 0)
            {
                zones[nbzones++] = MTGGameZone::MY_GRAVEYARD;
                zones[nbzones++] = MTGGameZone::OPPONENT_GRAVEYARD;
            }
            else if (zoneName.compare("battlefield") == 0 || zoneName.compare("inplay") == 0)
            {
                zones[nbzones++] = MTGGameZone::MY_BATTLEFIELD;
                zones[nbzones++] = MTGGameZone::OPPONENT_BATTLEFIELD;
            }
            else if (zoneName.compare("nonbattlezone") == 0)
            {
                zones[nbzones++] = MTGGameZone::MY_GRAVEYARD;
                zones[nbzones++] = MTGGameZone::OPPONENT_GRAVEYARD;
                zones[nbzones++] = MTGGameZone::MY_LIBRARY;
                zones[nbzones++] = MTGGameZone::OPPONENT_LIBRARY;
                zones[nbzones++] = MTGGameZone::MY_HAND;
                zones[nbzones++] = MTGGameZone::OPPONENT_HAND;
                zones[nbzones++] = MTGGameZone::OPPONENT_EXILE;
            }
            else if (zoneName.compare("stack") == 0)
            {
                zones[nbzones++] = MTGGameZone::MY_STACK;
                zones[nbzones++] = MTGGameZone::OPPONENT_STACK;
            }
            else
            {
                int zone = MTGGameZone::zoneStringToId(zoneName);
                if (zone) zones[nbzones++] = zone;
            }
        }
    }
    else
    {
        s1 = s;
        nbzones = 2;
        zones[0] = MTGGameZone::MY_BATTLEFIELD;
        zones[1] = MTGGameZone::OPPONENT_BATTLEFIELD;
    }

    TargetChooser * tc = NULL;
    int maxtargets = 1;
    CardDescriptor * cd = NULL;
    //max targets allowed
    size_t limit = s1.find('<');
    if (limit != string::npos) 
    {
        size_t end = s1.find(">", limit);
        string howmany;
        if (end != string::npos)
        {
            howmany = s1.substr(limit + 1, end - limit - 1);
            WParsedInt * howmuch = NEW WParsedInt(howmany, NULL, card);
            maxtargets = howmuch->getValue();
            delete howmuch;
            s1 = s1.substr(end + 1);
        }
    }

    while (s1.size())
    {
        found = s1.find(",");
        string typeName;
        if (found != string::npos)
        {
            typeName = s1.substr(0, found);
            s1 = s1.substr(found + 1);
        }
        else
        {
            typeName = s1;
            s1 = "";
        }

        //Advanced cards caracteristics ?
        found = typeName.find("[");
        if (found != string::npos)
        {
            int nbminuses = 0;
            int end = typeName.find("]");
            string attributes = typeName.substr(found + 1, end - found - 1);
            cd = NEW CardDescriptor();
            while (attributes.size())
            {
                size_t found2 = attributes.find(";");
                string attribute;
                if (found2 != string::npos)
                {
                    cd->mode = CD_OR;
                    attribute = attributes.substr(0, found2);
                    attributes = attributes.substr(found2 + 1);
                }
                else
                {
                    attribute = attributes;
                    attributes = "";
                }
                int minus = 0;
                if (attribute[0] == '-')
                {
                    minus = 1;
                    nbminuses++;
                    attribute = attribute.substr(1);
                }
                int comparisonMode = COMPARISON_NONE;
                int comparisonCriterion = 0;
                if (attribute.size() > 1)
                {
                    size_t operatorPosition = attribute.find("=", 1);
                    if (operatorPosition != string::npos)
                    {
                        string numberCD = attribute.substr(operatorPosition + 1, attribute.size() - operatorPosition - 1);
                        WParsedInt * val = NEW WParsedInt(numberCD,NULL, card);
                        comparisonCriterion = val->getValue();
                        /*atoi(attribute.substr(operatorPosition + 1, attribute.size() - operatorPosition - 1).c_str());*/
                        delete val;           
                        switch (attribute[operatorPosition - 1])
                        {
                        case '<':
                            if (minus)
                            {
                                comparisonMode = COMPARISON_GREATER;
                            }
                            else
                            {
                                comparisonMode = COMPARISON_AT_MOST;
                            }
                            operatorPosition--;
                            break;
                        case '>':
                            if (minus)
                            {
                                comparisonMode = COMPARISON_LESS;
                            }
                            else
                            {
                                comparisonMode = COMPARISON_AT_LEAST;
                            }
                            operatorPosition--;
                            break;
                        default:
                            if (minus)
                            {
                                comparisonMode = COMPARISON_UNEQUAL;
                            }
                            else
                            {
                                comparisonMode = COMPARISON_EQUAL;
                            }
                        }
                        attribute = attribute.substr(0, operatorPosition);
                    }
                }

                //Attacker
                if (attribute.find("attacking") != string::npos)
                {
                    if (minus)
                    {
                        cd->attacker = -1;
                    }
                    else
                    {
                        cd->attacker = 1;
                    }
                }
                //Blocker
                else if (attribute.find("blocking") != string::npos)
                {
                    if (minus)
                    {
                        cd->defenser = &MTGCardInstance::NoCard;
                    }
                    else
                    {
                        cd->defenser = &MTGCardInstance::AnyCard;
                    }
                }
                //Tapped, untapped
                else if (attribute.find("tapped") != string::npos)
                {
                    if (minus)
                    {
                        cd->unsecureSetTapped(-1);
                    }
                    else
                    {
                        cd->unsecureSetTapped(1);
                    }
                    //Token
                }
                else if (attribute.find("token") != string::npos)
                {
                    if (minus)
                    {
                        cd->isToken = -1;
                    }
                    else
                    {
                        cd->isToken = 1;
                    }
                    //put in its zone this turn
                }
                else if (attribute.find("fresh") != string::npos)
                {
                    if (minus)
                    {
                        cd->unsecuresetfresh(-1);
                    }
                    else
                    {
                        cd->unsecuresetfresh(1);
                    }
                }
                //creature is a level up creature
                else if (attribute.find("leveler") != string::npos)
                {
                    if (minus)
                    {
                        cd->isLeveler = -1;
                    }
                    else
                    {
                        cd->isLeveler = 1;
                    }
                }
                //creature is enchanted
                else if (attribute.find("enchanted") != string::npos)
                {
                    if (minus)
                    {
                        cd->CDenchanted = -1;
                    }
                    else
                    {
                        cd->CDenchanted = 1;
                    }
                }
                //creature was damaged
                else if (attribute.find("damaged") != string::npos)
                {
                    if (minus)
                    {
                        cd->CDdamaged = -1;
                    }
                    else
                    {
                        cd->CDdamaged = 1;
                    }
                }
                //creature dealt damage to opponent
                else if (attribute.find("opponentdamager") != string::npos)
                {
                    if (minus)
                    {
                        cd->CDopponentDamaged = -1;
                    }
                    else
                    {
                        cd->CDopponentDamaged = 1;
                    }
                }
                //creature dealt damage to controller
                else if (attribute.find("controllerdamager") != string::npos)
                {
                    if (minus)
                    {
                        cd->CDcontrollerDamaged = -1;
                    }
                    else
                    {
                        cd->CDcontrollerDamaged = 1;
                    }
                }
                else if (attribute.find("multicolor") != string::npos)
                {
                    //card is multicolored?
                    if (minus)
                    {
                        cd->setisMultiColored(-1);
                    }
                    else
                    {
                        cd->setisMultiColored(1);
                    }

                }
                else if (attribute.find("blackandgreen") != string::npos)
                {
                    //card is both colors?
                    if (minus)
                    {
                        cd->setisBlackAndGreen(-1);
                    }
                    else
                    {
                        cd->setisBlackAndGreen(1);
                    }

                }
                else if (attribute.find("blackandwhite") != string::npos)
                {
                    //card is both colors?
                    if (minus)
                    {
                        cd->setisBlackAndWhite(-1);
                    }
                    else
                    {
                        cd->setisBlackAndWhite(1);
                    }

                }
                else if (attribute.find("redandblue") != string::npos)
                {
                    //card is both colors?
                    if (minus)
                    {
                        cd->setisRedAndBlue(-1);
                    }
                    else
                    {
                        cd->setisRedAndBlue(1);
                    }

                }
                else if (attribute.find("blueandgreen") != string::npos)
                {
                    //card is both colors?
                    if (minus)
                    {
                        cd->setisBlueAndGreen(-1);
                    }
                    else
                    {
                        cd->setisBlueAndGreen(1);
                    }

                }
                else if (attribute.find("redandwhite") != string::npos)
                {
                    //card is both colors?
                    if (minus)
                    {
                        cd->setisRedAndWhite(-1);
                    }
                    else
                    {
                        cd->setisRedAndWhite(1);
                    }

                }
                else if (attribute.find("power") != string::npos)
                {
                    //Power restrictions
                    cd->setPower(comparisonCriterion);
                    cd->powerComparisonMode = comparisonMode;
                    //Toughness restrictions
                }
                else if (attribute.find("toughness") != string::npos)
                {
                    cd->setToughness(comparisonCriterion);
                    cd->toughnessComparisonMode = comparisonMode;
                    //Manacost restrictions
                }
                else if (attribute.find("manacost") != string::npos)
                {
                    cd->convertedManacost = comparisonCriterion;
                    cd->manacostComparisonMode = comparisonMode;
                    //Counter Restrictions
                }
                else if (attribute.find("share!") != string::npos)
                {
                    size_t start = attribute.find("share!");
                    size_t end = attribute.find("!");
                    string CDtype = attribute.substr(start + 6,end - start);
                    if(card->isSpell() && card->backupTargets[0]->typeAsTarget() == TARGET_STACKACTION)
                    {
                    //spells always store their targets in :targets[]
                    //however they are all erased as the spell resolves
                    //added a array to store these backups incase theyre needed
                    //again for effects such as these.
                        Spell * spell;
                        spell = (Spell*)card->backupTargets[0];
                        card->target = spell->source;
                    }
                    if( CDtype.find("name") != string::npos )
                    {
                        if(card->target)
                            cd->compareName = card->target->name;
                        else
                            cd->compareName = card->name;

                        cd->nameComparisonMode = COMPARISON_EQUAL;
                    }
                    else if( CDtype.find("color") != string::npos )
                    {
                        for(int i = 0; i < Constants::MTG_NB_COLORS; i++)
                        {
                            if(card->target)
                                cd->colors[i] = card->target->colors[i];
                            else
                                cd->colors[i] = card->colors[i];
                        }
                        cd->mode = CD_OR;
                    }
                    else if( CDtype.find("types") != string::npos )
                    {
                        if(card->target)
                        {
                            cd->types = card->target->types;
                            //remove main types because we only care about subtypes here.
                            cd->removeType("artifact");
                            cd->removeType("land");
                            cd->removeType("enchantment");
                            cd->removeType("instant");
                            cd->removeType("sorcery");
                            cd->removeType("legendary");
                            cd->removeType("creature");
                        }
                        else
                        {
                            cd->types = card->types;
                        }
                        cd->mode = CD_OR;
                    }
                }
                else if (attribute.find("counter") != string::npos)
                {
                    if (attribute.find("{any}") != string::npos)
                    {
                        cd->anyCounter = 1;
                    }
                    else
                    {
                        size_t start = attribute.find("{");
                        size_t end = attribute.find("}");
                        string counterString = attribute.substr(start + 1, end - start - 1);
                        AbilityFactory abf;
                        Counter * counter = abf.parseCounter(counterString, card);
                        if (counter)
                        {
                            cd->counterName = counter->name;
                            cd->counterNB = counter->nb;
                            cd->counterPower = counter->power;
                            cd->counterToughness = counter->toughness;
                            delete (counter);
                        }
                        if (minus)
                        {
                            cd->counterComparisonMode = COMPARISON_LESS;
                        }
                        else
                        {
                            cd->counterComparisonMode = COMPARISON_AT_LEAST;
                        }
                    }
                }
                else
                {
                    int attributefound = 0;
                    //Colors
                    for (int cid = 1; cid < Constants::MTG_NB_COLORS - 1; cid++)
                    { //remove Artifact and Land from the loop
                        if (attribute.find(Constants::MTGColorStrings[cid]) != string::npos)
                        {
                            attributefound = 1;
                            if (minus)
                            {
                                cd->colors[cid] = -1;
                            }
                            else
                            {
                                cd->colors[cid] = 1;
                            }
                        }
                    }
                    if (!attributefound)
                    {
                        //Abilities
                        for (int j = 0; j < Constants::NB_BASIC_ABILITIES; j++)
                        {
                            if (attribute.find(Constants::MTGBasicAbilities[j]) != string::npos)
                            {
                                attributefound = 1;
                                if (minus)
                                {
                                    cd->basicAbilities[j] = -1;
                                }
                                else
                                {
                                    cd->basicAbilities[j] = 1;
                                }
                            }
                        }
                    }

                    if (!attributefound)
                    {
                        //Subtypes
                        if (minus)
                        {
                            cd->setNegativeSubtype(attribute);
                        }
                        else
                        {
                            cd->setSubtype(attribute);
                        }
                    }
                }
            }
            if (nbminuses) cd->mode = CD_AND;
            typeName = typeName.substr(0, found);
        }
        //X targets allowed ?
        if (typeName.at(typeName.length() - 1) == 's' && !Subtypes::subtypesList->find(typeName, false) && typeName.compare("this")
                        != 0)
        {
            typeName = typeName.substr(0, typeName.length() - 1);
            maxtargets = -1;
        }
        if (cd)
        {
            if (!tc)
            {
                if (typeName.compare("*") != 0) cd->setSubtype(typeName);

                tc = NEW DescriptorTargetChooser(cd, zones, nbzones, card, maxtargets, other);
            }
            else
            {
                delete (cd);
                return NULL;
            }
        }
        else
        {
            if (!tc)
            {
                if (typeName.compare("*") == 0)
                {
                    return NEW TargetZoneChooser(zones, nbzones, card, maxtargets, other);
                }
                else if (typeName.compare("this") == 0)
                {
                    return NEW CardTargetChooser(card, card, zones, nbzones);
                }
                else
                {
                    tc = NEW TypeTargetChooser(typeName.c_str(), zones, nbzones, card, maxtargets, other);
                }
            }
            else
            {
                ((TypeTargetChooser *) tc)->addType(typeName.c_str());
                tc->maxtargets = maxtargets;
            }
        }
    }
    return tc;
}

TargetChooser * TargetChooserFactory::createTargetChooser(MTGCardInstance * card)
{
    if(!card)
        return NULL;
    int id = card->getId();
    string s = card->spellTargetType;
    if (card->alias)
    {
        id = card->alias;
        //TODO load target as well... ?
    }
    TargetChooser * tc = createTargetChooser(s, card);
    if (tc) return tc;
    //Any target than cannot be defined automatically is determined by its id
    switch (id)
    {
    //Spell
    case 1224: //Spell blast
    {
#if defined (WIN32) || defined (LINUX)
        DebugTrace("Counter Spell !\n");
#endif
        return NEW SpellTargetChooser(card);
    }
        //Spell Or Permanent
    case 1282: //ChaosLace
    case 1152: //DeathLace
    case 1358: //PureLace
    case 1227: //ThoughLace
    case 1257: //Lifelace
    {
        return NEW SpellOrPermanentTargetChooser(card);
    }
        //Red Spell or Permanent
    case 1191: //Blue Elemental Blast
    {
        return NEW SpellOrPermanentTargetChooser(card, Constants::MTG_COLOR_RED);
    }
        //Blue Spell or Permanent
    case 1312: //Red Elemental Blast
    {
        return NEW SpellOrPermanentTargetChooser(card, Constants::MTG_COLOR_BLUE);
    }
        //Damage History
    case 1344: //Eye for an Eye
    {
        return NEW DamageTargetChooser(card, -1, 1, RESOLVED_OK);
    }
    default:
    {
        return NULL;
    }
    }
}

TargetChooser::TargetChooser(MTGCardInstance * card, int _maxtargets, bool _other) :
    TargetsList()
{
    forceTargetListReady = 0;
    source = card;
    targetter = card;
    maxtargets = _maxtargets;
    other = _other;
}

//Default targetter : every card can be targetted, unless it is protected from the targetter card
// For spells that do not "target" a specific card, set targetter to NULL
bool TargetChooser::canTarget(Targetable * target)
{
    if (!target) return false;
    if (target->typeAsTarget() == TARGET_CARD)
    {
        MTGCardInstance * card = (MTGCardInstance *) target;
        if (other)
        {
            MTGCardInstance * tempcard = card;
            while (tempcard)
            {
                if (tempcard == source) return false;
                tempcard = tempcard->previous;
            }
        }
        if(source && ((source->hasSubtype("aura") || source->hasSubtype("equipment")) && source->target && source->target == card && source->target->isPhased && targetter->target == card)) 
        return true;
        //this is kinda cheating but by default we let auras and equipments always continue to target a phased creature.
        else if(card->isPhased) 
        return false;
        if (source && targetter && card->isInPlay()) 
        { 
            if (card->has(Constants::SHROUD)) return false;
            if (card->protectedAgainst(targetter)) return false;
            if (card->CantBeTargetby(targetter)) return false;
            if ((targetter->controller() != card->controller()) && card->has(Constants::OPPONENTSHROUD)) return false;
        }
        return true;
    }
    else if (target->typeAsTarget() == TARGET_STACKACTION) return true;
    return false;
}

int TargetChooser::addTarget(Targetable * target)
{
    if (canTarget(target))
    {
        TargetsList::addTarget(target);
    }

    return targetsReadyCheck();
}

int TargetChooser::ForceTargetListReady()
{
    int state = targetsReadyCheck();
    if (state == TARGET_OK)
    {
        forceTargetListReady = 1;
    }
    return forceTargetListReady;
}

int TargetChooser::targetsReadyCheck()
{
    if (cursor <= 0)
    {
        return TARGET_NOK;
    }
    if (full())
    {
        return TARGET_OK_FULL;
    }
    if (!ready())
    {
        return TARGET_OK_NOT_READY;
    }
    return TARGET_OK;
}

int TargetChooser::targetListSet()
{
    int state = targetsReadyCheck();
    if (state == TARGET_OK_FULL || forceTargetListReady)
    {
        return 1;
    }
    return 0;
}

bool TargetChooser::validTargetsExist()
{
    for (int i = 0; i < 2; ++i)
    {
        Player *p = GameObserver::GetInstance()->players[i];
        if (canTarget(p)) return true;
        MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->exile };
        for (int k = 0; k < 5; k++)
        {
            MTGGameZone * z = zones[k];
            if (targetsZone(z))
            {
                for (int j = 0; j < z->nb_cards; j++)
                {
                    if (canTarget(z->cards[j])) return true;
                }
            }
        }
    }
    return false;
}

bool TargetChooser::equals(TargetChooser * tc)
{

    //This function always return 1 for now, since the default TargetChooser targets everything
    //In the future we might need to check some of "targetter" settings to take protection into account...
    return true;
}

/**
 a specific Card
 **/
CardTargetChooser::CardTargetChooser(MTGCardInstance * _card, MTGCardInstance * source, int * _zones, int _nbzones) :
    TargetZoneChooser(_zones, _nbzones, source)
{
    validTarget = _card;
}

bool CardTargetChooser::canTarget(Targetable * target)
{
    if (!target) return false;
    if (target->typeAsTarget() != TARGET_CARD) return false;
    if (!nbzones && !TargetChooser::canTarget(target)) return false;
    if (nbzones && !TargetZoneChooser::canTarget(target)) return false;
    MTGCardInstance * card = (MTGCardInstance *) target;
    while (card)
    {
        if (card == validTarget) return true;
        card = card->previous;
    }
    return false;
}

CardTargetChooser * CardTargetChooser::clone() const
{
    CardTargetChooser * a = NEW CardTargetChooser(*this);
    return a;
}

bool CardTargetChooser::equals(TargetChooser * tc)
{

    CardTargetChooser * ctc = dynamic_cast<CardTargetChooser *> (tc);
    if (!ctc)
        return false;
    
    if (validTarget != ctc->validTarget) //todo, check also previous cards, see "cantarget"...
        return false;

    return TargetZoneChooser::equals(tc);
}


/**
 Choose anything that has a given list of types
 **/
TypeTargetChooser::TypeTargetChooser(const char * _type, MTGCardInstance * card, int _maxtargets, bool other) :
    TargetZoneChooser(card, _maxtargets, other)
{
    int id = Subtypes::subtypesList->find(_type);
    nbtypes = 0;
    addType(id);
    int default_zones[] = { MTGGameZone::MY_BATTLEFIELD, MTGGameZone::OPPONENT_BATTLEFIELD };
    init(default_zones, 2);
}

TypeTargetChooser::TypeTargetChooser(const char * _type, int * _zones, int nbzones, MTGCardInstance * card, int _maxtargets,
                bool other) :
    TargetZoneChooser(card, _maxtargets, other)
{
    int id = Subtypes::subtypesList->find(_type);
    nbtypes = 0;
    addType(id);
    if (nbzones == 0)
    {
        int default_zones[] = { MTGGameZone::MY_BATTLEFIELD, MTGGameZone::OPPONENT_BATTLEFIELD };
        init(default_zones, 2);
    }
    else
    {
        init(_zones, nbzones);
    }
}

void TypeTargetChooser::addType(const char * _type)
{
    int id = Subtypes::subtypesList->find(_type);
    addType(id);
}

void TypeTargetChooser::addType(int type)
{
    types[nbtypes] = type;
    nbtypes++;
}

bool TypeTargetChooser::canTarget(Targetable * target)
{
    if (!TargetZoneChooser::canTarget(target)) return false;
    if (target->typeAsTarget() == TARGET_CARD)
    {
        MTGCardInstance * card = (MTGCardInstance *) target;
        for (int i = 0; i < nbtypes; i++)
        {
            if (card->hasSubtype(types[i])) return true;
            if (Subtypes::subtypesList->find(card->getLCName()) == types[i]) return true;
        }
        return false;
    }
    else if (target->typeAsTarget() == TARGET_STACKACTION)
    {
        Interruptible * action = (Interruptible *) target;
        if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
        {
            Spell * spell = (Spell *) action;
            MTGCardInstance * card = spell->source;
            for (int i = 0; i < nbtypes; i++)
            {
                if (card->hasSubtype(types[i])) return true;
                if (Subtypes::subtypesList->find(card->name) == types[i]) return true;
            }
            return false;
        }
    }
    return false;
}

TypeTargetChooser * TypeTargetChooser::clone() const
{
    TypeTargetChooser * a = NEW TypeTargetChooser(*this);
    return a;
}

bool TypeTargetChooser ::equals(TargetChooser * tc)
{

    TypeTargetChooser  * ttc = dynamic_cast<TypeTargetChooser  *> (tc);
    if (!ttc)
        return false;
    
    if (nbtypes != ttc->nbtypes)
        return false;

    map<int,int> counts;

    for (int i = 0; i < nbtypes; ++i)
    {
        counts[types[i]] +=1;
        counts[ttc->types[i]] -=1;
    }

    for (int i = 0; i < nbtypes; ++i)
    {
        if (counts[types[i]] || counts[ttc->types[i]])
            return false;
    }

    return TargetZoneChooser::equals(tc);
}


/**
 A Target Chooser associated to a Card Descriptor object, for fine tuning of targets description
 **/
DescriptorTargetChooser::DescriptorTargetChooser(CardDescriptor * _cd, MTGCardInstance * card, int _maxtargets, bool other) :
    TargetZoneChooser(card, _maxtargets, other)
{
    int default_zones[] = { MTGGameZone::MY_BATTLEFIELD, MTGGameZone::OPPONENT_BATTLEFIELD };
    init(default_zones, 2);
    cd = _cd;
}

DescriptorTargetChooser::DescriptorTargetChooser(CardDescriptor * _cd, int * _zones, int nbzones, MTGCardInstance * card,
                int _maxtargets, bool other) :
    TargetZoneChooser(card, _maxtargets, other)
{
    if (nbzones == 0)
    {
        int default_zones[] = { MTGGameZone::MY_BATTLEFIELD, MTGGameZone::OPPONENT_BATTLEFIELD };
        init(default_zones, 2);
    }
    else
    {
        init(_zones, nbzones);
    }
    cd = _cd;
}

bool DescriptorTargetChooser::canTarget(Targetable * target)
{
    if (!TargetZoneChooser::canTarget(target)) return false;
    if (target->typeAsTarget() == TARGET_CARD)
    {
        MTGCardInstance * _target = (MTGCardInstance *) target;
        if (cd->match(_target)) 
        return true;
    }
    else if (target->typeAsTarget() == TARGET_STACKACTION)
    {
        Interruptible * action = (Interruptible *) target;
        if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
        {
            Spell * spell = (Spell *) action;
            MTGCardInstance * card = spell->source;
            if (cd->match(card)) return true;
        }
    }
    return false;
}

DescriptorTargetChooser::~DescriptorTargetChooser()
{
    SAFE_DELETE(cd);
}

DescriptorTargetChooser * DescriptorTargetChooser::clone() const
{
    DescriptorTargetChooser * a = NEW DescriptorTargetChooser(*this);
    a->cd = NEW CardDescriptor(*cd);
    return a;
}

bool DescriptorTargetChooser::equals(TargetChooser * tc)
{

    DescriptorTargetChooser  * dtc = dynamic_cast<DescriptorTargetChooser  *> (tc);
    if (!dtc)
        return false;
    
    //TODO Descriptors need to have an "equals" method too -_-

    return TargetZoneChooser::equals(tc);
}

/* TargetzoneChooser targets everything in a given zone */
TargetZoneChooser::TargetZoneChooser(MTGCardInstance * card, int _maxtargets, bool other) :
    TargetChooser(card, _maxtargets, other)
{
    init(NULL, 0);
}

TargetZoneChooser::TargetZoneChooser(int * _zones, int _nbzones, MTGCardInstance * card, int _maxtargets, bool other) :
    TargetChooser(card, _maxtargets, other)
{
    init(_zones, _nbzones);
}

int TargetZoneChooser::init(int * _zones, int _nbzones)
{
    for (int i = 0; i < _nbzones; i++)
    {
        zones[i] = _zones[i];
    }
    nbzones = _nbzones;
    return nbzones;
}

int TargetZoneChooser::setAllZones()
{
    int zones[] = { MTGGameZone::ALL_ZONES, };

    init(zones, 1);
    return 1;
}

bool TargetZoneChooser::canTarget(Targetable * target)
{
    if (!TargetChooser::canTarget(target)) return false;
    if (target->typeAsTarget() == TARGET_CARD)
    {
        MTGCardInstance * card = (MTGCardInstance *) target;
        for (int i = 0; i < nbzones; i++)
        {
            if (zones[i] == MTGGameZone::ALL_ZONES) return true;
            if (MTGGameZone::intToZone(zones[i], source, card)->hasCard(card)) return true;
        }
    }
    else if (target->typeAsTarget() == TARGET_STACKACTION)
    {
        DebugTrace("CHECKING INTERRUPTIBLE\n");
        Interruptible * action = (Interruptible *) target;
        if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
        {
            Spell * spell = (Spell *) action;
            MTGCardInstance * card = spell->source;
            for (int i = 0; i < nbzones; i++)
                if (MTGGameZone::intToZone(zones[i], source, card)->hasCard(card)) return true;
        }
    }
    return false;
}

bool TargetZoneChooser::targetsZone(MTGGameZone * z)
{
    for (int i = 0; i < nbzones; i++)
        if (MTGGameZone::intToZone(zones[i], source) == z) return true;
    return false;
}

TargetZoneChooser * TargetZoneChooser::clone() const
{
    TargetZoneChooser * a = NEW TargetZoneChooser(*this);
    return a;
}

bool TargetZoneChooser::equals(TargetChooser * tc)
{

    TargetZoneChooser  * tzc = dynamic_cast<TargetZoneChooser  *> (tc);
    if (!tzc)
        return false;
    
    if (nbzones!= tzc->nbzones)
        return false;

    map<int,int> counts;

    for (int i = 0; i < nbzones; ++i)
    {
        counts[zones[i]] +=1;
        counts[tzc->zones[i]] -=1;
    }

    for (int i = 0; i < nbzones; ++i)
    {
        if (counts[zones[i]] || counts[tzc->zones[i]])
            return false;
    }

    //TODO: ALL_ZONES should be equivalent to something actually targetting all zones...

    return TargetChooser::equals(tc);
}

/* Player Target */
PlayerTargetChooser::PlayerTargetChooser(MTGCardInstance * card, int _maxtargets, Player *p) :
    TargetChooser(card, _maxtargets), p(p)
{
}

bool PlayerTargetChooser::canTarget(Targetable * target)
{
    if (source && targetter && (targetter->controller() != targetter->controller()->opponent())
                    && (targetter->controller()->opponent()->game->inPlay->hasAbility(Constants::CONTROLLERSHROUD))
                    && targetter->controller() != target) return false;
    if (source && targetter && (targetter->controller() == targetter->controller())
                    && (targetter->controller()->opponent()->game->inPlay->hasAbility(Constants::PLAYERSHROUD))
                    && targetter->controller()->opponent() == target) return false;
    if (source && targetter && (targetter->controller() == targetter->controller())
                    && (targetter->controller()->game->inPlay->hasAbility(Constants::PLAYERSHROUD)) && targetter->controller()
                    == target) return false;
    return (target->typeAsTarget() == TARGET_PLAYER) && (!p || p == (Player*) target);
}

PlayerTargetChooser* PlayerTargetChooser::clone() const
{
    PlayerTargetChooser * a = NEW PlayerTargetChooser(*this);
    return a;
}

bool PlayerTargetChooser::equals(TargetChooser * tc)
{

    PlayerTargetChooser  * ptc = dynamic_cast<PlayerTargetChooser   *> (tc);
    if (!ptc)
        return false;
    
    if (p != ptc->p)
        return false;

    return TargetChooser::equals(tc);
}

/*Damageable Target */
bool DamageableTargetChooser::canTarget(Targetable * target)
{
    if (source && targetter && (targetter->controller() != targetter->controller()->opponent())
                    && (targetter->controller()->opponent()->game->inPlay->hasAbility(Constants::CONTROLLERSHROUD))
                    && targetter->controller() != target) return false;
    if (source && targetter && (targetter->controller() == targetter->controller())
                    && (targetter->controller()->opponent()->game->inPlay->hasAbility(Constants::PLAYERSHROUD))
                    && targetter->controller()->opponent() == target) return false;
    if (source && targetter && (targetter->controller() == targetter->controller())
                    && (targetter->controller()->game->inPlay->hasAbility(Constants::PLAYERSHROUD)) && targetter->controller()
                    == target) return false;
    if (target->typeAsTarget() == TARGET_PLAYER)
    {
        return true;
    }
    return TypeTargetChooser::canTarget(target);
}

DamageableTargetChooser* DamageableTargetChooser::clone() const
{
    DamageableTargetChooser * a = NEW DamageableTargetChooser(*this);
    return a;
}

bool DamageableTargetChooser::equals(TargetChooser * tc)
{

    DamageableTargetChooser  * dtc = dynamic_cast<DamageableTargetChooser  *> (tc);
    if (!dtc)
        return false;
    
    return TypeTargetChooser::equals(tc);
}

/*Spell */

SpellTargetChooser::SpellTargetChooser(MTGCardInstance * card, int _color, int _maxtargets, bool other) :
    TargetChooser(card, _maxtargets, other)
{
    color = _color;
}

bool SpellTargetChooser::canTarget(Targetable * target)
{
    MTGCardInstance * card = NULL;
    if (target->typeAsTarget() == TARGET_STACKACTION)
    {
        Interruptible * action = (Interruptible *) target;
        if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
        {
            Spell * spell = (Spell *) action;
            card = spell->source;
            if (card && (color == -1 || card->hasColor(color))) return true;
        }
    }

    return false;
}

SpellTargetChooser* SpellTargetChooser::clone() const
{
    SpellTargetChooser * a = NEW SpellTargetChooser(*this);
    return a;
}

bool SpellTargetChooser::equals(TargetChooser * tc)
{

    SpellTargetChooser  * stc = dynamic_cast<SpellTargetChooser *> (tc);
    if (!stc)
        return false;
    
    if (color != stc->color)
        return false;

    return TargetChooser::equals(tc);
}

/*Spell or Permanent */
SpellOrPermanentTargetChooser::SpellOrPermanentTargetChooser(MTGCardInstance * card, int _color, int _maxtargets, bool other) :
    TargetZoneChooser(card, _maxtargets, other)
{
    int default_zones[] = { MTGGameZone::MY_BATTLEFIELD, MTGGameZone::OPPONENT_BATTLEFIELD };
    init(default_zones, 2);
    color = _color;
}

bool SpellOrPermanentTargetChooser::canTarget(Targetable * target)
{
    MTGCardInstance * card = NULL;
    if (target->typeAsTarget() == TARGET_CARD)
    {
        card = (MTGCardInstance *) target;
        if (color == -1 || card->hasColor(color)) return TargetZoneChooser::canTarget(target);
    }
    else if (target->typeAsTarget() == TARGET_STACKACTION)
    {
        Interruptible * action = (Interruptible *) target;
        if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
        {
            Spell * spell = (Spell *) action;
            card = spell->source;
            if (card && (color == -1 || card->hasColor(color))) return true;
        }
    }
    return false;
}

SpellOrPermanentTargetChooser* SpellOrPermanentTargetChooser::clone() const
{
    SpellOrPermanentTargetChooser * a = NEW SpellOrPermanentTargetChooser(*this);
    return a;
}

bool SpellOrPermanentTargetChooser::equals(TargetChooser * tc)
{

    SpellOrPermanentTargetChooser  * sptc = dynamic_cast<SpellOrPermanentTargetChooser *> (tc);
    if (!sptc)
        return false;
    
    if (color != sptc->color)
        return false;

    return TargetZoneChooser::equals(tc);
}

/*Damage */
DamageTargetChooser::DamageTargetChooser(MTGCardInstance * card, int _color, int _maxtargets, int _state) :
    TargetChooser(card, _maxtargets)
{
    color = _color;
    state = _state;
}

bool DamageTargetChooser::canTarget(Targetable * target)
{
    MTGCardInstance * card = NULL;
    if (target->typeAsTarget() == TARGET_STACKACTION)
    {
        Interruptible * action = (Interruptible *) target;
        if (action->type == ACTION_DAMAGE && (action->state == state || state == -1))
        {
            Damage * damage = (Damage *) action;
            card = damage->source;
            if (card && (color == -1 || card->hasColor(color))) return true;
        }
    }
    return false;
}

DamageTargetChooser* DamageTargetChooser::clone() const
{
    DamageTargetChooser * a = NEW DamageTargetChooser(*this);
    return a;
}

bool DamageTargetChooser::equals(TargetChooser * tc)
{

    DamageTargetChooser * dtc = dynamic_cast<DamageTargetChooser *> (tc);
    if (!dtc)
        return false;

    if (color != dtc->color || state != dtc->state)
        return false;

    return TargetChooser::equals(tc);
}

TriggerTargetChooser::TriggerTargetChooser(int _triggerTarget)
{
    triggerTarget = _triggerTarget;
    target = NULL;
}

bool TriggerTargetChooser::targetsZone(MTGGameZone * z)
{
    return true;
}

bool TriggerTargetChooser::canTarget(Targetable * _target)
{
    if (_target == target) return true;
    return false;
}

TriggerTargetChooser * TriggerTargetChooser::clone() const
{
    TriggerTargetChooser * a = NEW TriggerTargetChooser(*this);
    return a;
}

bool TriggerTargetChooser::equals(TargetChooser * tc)
{

    TriggerTargetChooser * ttc = dynamic_cast<TriggerTargetChooser *> (tc);
    if (!ttc)
        return false;

    if (triggerTarget != ttc->triggerTarget)
        return false;

    return TargetChooser::equals(tc);
}