class CardCaravan : public CardEffect
{
public:
    void ProcessDuration(State &s) const
    {
        if(logging) s.Log("draws a card from " + c->PrettyName());
        s.stack.PushEnd(new EventDrawCard(s.player));
    }
};

class CardLighthouse : public CardEffect
{
public:
    void ProcessDuration(State &s) const
    {
        if(logging) s.Log("gets $1 from " + c->PrettyName());
        s.players[s.player].money++;
    }
};

class CardTactician : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        PlayerState &p = s.players[s.player];
        if(p.hand.Length() == 0)
        {
            if(logging) s.LogIndent(1, "has no cards to discard");
            for(CardPlayInfo &playCard : p.playArea)
            {
                if(playCard.card == c && playCard.copies > 0 && playCard.turnsLeft > 0)
                {
                    //
                    // TODO: This may be a conservative implementation of tactician
                    //
                    playCard.copies--;
                    return;
                }
            }
        }
        for(Card *c : p.hand) s.stack.PushEnd(new EventDiscardCard(s.player, c, DiscardFromHand));
    }
    void ProcessDuration(State &s) const
    {
        if(logging) s.Log("draws 5 cards from " + c->PrettyName());

        PlayerState &p = s.players[s.player];
        p.actions++;
        p.buys++;
        for(int i = 0; i < 5; i++) s.stack.PushEnd(new EventDrawCard(s.player));
    }
};

class CardFishingVillage : public CardEffect
{
public:
    void ProcessDuration(State &s) const
    {
        if(logging) s.Log("gets $1 from " + c->PrettyName());
        s.players[s.player].actions++;
        s.players[s.player].money++;
    }
};

class CardMerchantShip : public CardEffect
{
public:
    void ProcessDuration(State &s) const
    {
        if(logging) s.Log("gets $2 from " + c->PrettyName());
        s.players[s.player].money += 2;
    }
};

class CardWharf : public CardEffect
{
public:
    void ProcessDuration(State &s) const
    {
        if(logging) s.Log("draws 2 cards from " + c->PrettyName());
        s.stack.PushEnd(new EventDrawCard(s.player));
        s.stack.PushEnd(new EventDrawCard(s.player));
        s.players[s.player].buys++;
    }
};

class CardWarehouse : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.stack.PushEnd(new EventDiscardNCards(c, s.player, 3, 3));
    }
};

class CardPearlDiver : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        PlayerState &p = s.players[s.player];
        if(p.deck.Length() == 0) s.Shuffle(s.player);
        if(p.deck.Length() == 0)
        {
            if(logging) s.LogIndent(1, "has no cards to reveal");
        }
        else
        {
            Card *revealedCard = p.deck.First();
            if(logging) s.LogIndent(1, "reveals " + revealedCard->PrettyName() + " from the bottom of their deck");
            s.decision.MakeDiscreteChoice(c, 2);
            if(decisionText) s.decision.text = "Put" + revealedCard->PrettyName() + "on top of your deck?|Yes|No";
        }
    }
    bool CanProcessDecisions() const
    {
        return true;
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        PlayerState &p = s.players[s.player];
        Card *revealedCard = p.deck.First();
        if(response.choice == 0)
        {
            if(logging) s.LogIndent(1, "puts " + revealedCard->PrettyName() + " on top of their deck");
            p.deck.RemoveSlow(0);
            p.deck.PushEnd(revealedCard);
        }
        else
        {
            if(logging) s.LogIndent(1, "leaves " + revealedCard->PrettyName() + " on the bottom of their deck");
        }
    }
};

class CardLookout : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.stack.PushEnd(new EventLookout(c));
    }
};

class CardNavigator : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.stack.PushEnd(new EventNavigator(c));
    }
};

class CardSalvager : public CardEffect
{
public:
    bool CanProcessDecisions() const
    {
        return true;
    }
    void PlayAction(State &s) const
    {
        PlayerState &p = s.players[s.player];
        if(p.hand.Length() == 0)
        {
            if(logging) s.LogIndent(1, "has no cards to trash");
            return;
        }

        s.decision.SelectCards(c, 1, 1);
        if(decisionText) s.decision.text = "Choose a card to trash:";
        s.decision.AddUniqueCards(p.hand);
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        for(Card *c : response.cards)
        {
            s.stack.PushEnd(new EventTrashCardFromHand(s.player, c));
            int value = s.SupplyCost(c);
            s.players[s.player].money += value;
            if(logging) s.LogIndent(1, "gets $" + String(value));
        }
    }
};

class CardSeaHag : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        for(const PlayerInfo &p : s.data->players)
        {
            if(p.index != s.player)
            {
                s.stack.PushEnd(new EventSeaHagAttack(c, p.index));
            }
        }
    }
};

class CardAmbassador : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.stack.PushEnd(new EventAmbassador(c));
    }
};

class CardGhostShip : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        for(const PlayerInfo &p : s.data->players)
        {
            if(p.index != s.player)
            {
                s.stack.PushEnd(new EventPutOnDeckDownToN(c, p.index, 3));
            }
        }
    }
};

class CardCutpurse : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        for(const PlayerInfo &p : s.data->players)
        {
            if(p.index != s.player)
            {
                s.stack.PushEnd(new EventCutpurseAttack(c, p.index));
            }
        }
    }
};

class CardSmugglers : public CardEffect
{
public:
    bool CanProcessDecisions() const
    {
        return true;
    }
    void PlayAction(State &s) const
    {
        PlayerState &p = s.players[s.player];

        s.decision.SelectCards(c, 1, 1);
        for(Card *c : s.prevGainList)
        {
            if(s.SupplyCost(c) <= 6) s.decision.AddUniqueCard(c);
        }

        if(s.decision.cardChoices.Length() == 0)
        {
            s.decision.type = DecisionNone;
            if(logging) s.LogIndent(1, "cannot gain any cards");
            return;
        }

        if(decisionText) s.decision.text = "Choose a card to gain:";
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        s.stack.PushEnd(new EventGainCard(s.player, response.cards[0]));
    }
};

class CardTreasureMap : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        PlayerState &p = s.players[s.player];
        s.stack.PushEnd(new EventTrashCardFromPlay(s.player, c));

        if(p.hand.Contains(s.data->baseCards.treasureMap))
        {
            s.stack.PushEnd(new EventTrashCardFromHand(s.player, c));
            for(int goldIndex = 0; goldIndex < 4; goldIndex++)
            {
                s.stack.PushEnd(new EventGainCard(s.player, s.data->baseCards.gold, false, false, GainToDeckTop));
            }
        }
    }
};

class CardExplorer : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.stack.PushEnd(new EventExplorer(c));
    }
};

class CardIsland : public CardEffect
{
public:
    bool CanProcessDecisions() const
    {
        return true;
    }
    void PlayAction(State &s) const
    {
        PlayerState &p = s.players[s.player];

        bool islandFound = false;
        for(UINT playIndex = 0; playIndex < p.playArea.Length(); playIndex++)
        {
            if(p.playArea[playIndex].card == c)
            {
                p.playArea.RemoveSwap(playIndex);
                p.islandZone.PushEnd(c);
                islandFound = true;
            }
        }

        if(!islandFound)
        {
            //
            // Sometimes we will not find the island in play because of throne room
            //
            return;
        }

        if(p.hand.Length() == 0)
        {
            if(logging) s.LogIndent(1, "has no cards in hand to set aside");
            return;
        }

        s.decision.SelectCards(c, 1, 1);
        s.decision.AddUniqueCards(p.hand);
        if(decisionText) s.decision.text = "Choose a card to set aside:";
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        PlayerState &p = s.players[s.player];
        Card *asideCard = response.cards[0];
        p.hand.RemoveSwap(p.hand.FindFirstIndex(asideCard));
        p.islandZone.PushEnd(response.cards[0]);
        if(logging) s.LogIndent(1, "sets " + asideCard->PrettyName() + " aside");
    }
};