class CardChapel : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        if(s.players[s.player].hand.Length() > 0)
        {
            s.decision.SelectCards(c, 0, 4);
            if(decisionText) s.decision.text = "Select up to 4 cards to trash:";
            s.decision.cardChoices = s.players[s.player].hand;
        }
        else
        {
            if(logging) s.Log("has no cards to trash");
        }
    }
    bool CanProcessDecisions() const
    {
        return true;
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        for(Card *c : response.cards)
        {
            s.stack.PushEnd(new EventTrashCardFromHand(s.player, c));
        }
    }
};

class CardCellar : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        if(s.players[s.player].hand.Length() > 0)
        {
            s.decision.SelectCards(c, 0, s.players[s.player].hand.Length());
            if(decisionText) s.decision.text = "Select cards to discard:";
            s.decision.cardChoices = s.players[s.player].hand;
        }
        else
        {
            if(logging) s.Log("has no cards to discard");
        }
    }
    bool CanProcessDecisions() const
    {
        return true;
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        for(Card *c : response.cards)
        {
            s.stack.PushEnd(new EventDrawCard(s.player));
        }
        for(Card *c : response.cards)
        {
            s.stack.PushEnd(new EventDiscardCard(s.player, c, DiscardFromHand));
        }
    }
};

class CardMoneylender : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        if(s.players[s.player].hand.FindFirstIndex(s.data->baseCards.copper) != -1)
        {
            s.stack.PushEnd(new EventTrashCardFromHand(s.player, s.data->baseCards.copper));
            s.players[s.player].money += 3;
        }
        else
        {
            if(logging) s.Log("has no coppers to trash");
        }
    }
};

class CardWorkshop : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.decision.GainCardFromSupply(s, c, 0, 4);
        if(s.decision.cardChoices.Length() == 0)
        {
            s.decision.type = DecisionNone;
            if(logging) s.Log("has no cards to gain");
        }
    }
    bool CanProcessDecisions() const
    {
        return true;
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        s.data->players[s.decision.controllingPlayer].ledger.RecordBuy(response.cards[0]);
        s.stack.PushEnd(new EventGainCard(s.player, response.cards[0]));
    }
};

class CardFeast : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.decision.GainCardFromSupply(s, c, 0, 5);
        if(s.decision.cardChoices.Length() == 0)
        {
            s.decision.type = DecisionNone;
            if(logging) s.Log("has no cards to gain");
        }
    }
    bool CanProcessDecisions() const
    {
        return true;
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        s.data->players[s.decision.controllingPlayer].ledger.RecordBuy(response.cards[0]);
        s.stack.PushEnd(new EventTrashCardFromPlay(s.player, c));
        s.stack.PushEnd(new EventGainCard(s.player, response.cards[0]));
    }
};

class CardChancellor : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.decision.MakeDiscreteChoice(c, 2);
        if(decisionText) s.decision.text = "Put your deck into your discard pile?|Yes|No";
    }
    bool CanProcessDecisions() const
    {
        return true;
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        if(response.choice == 0)
        {
            if(logging) s.Log("discards their deck");
            PlayerState &p = s.players[s.player];
            p.discard.Append(p.deck);
            p.deck.FreeMemory();
        }
        else
        {
            if(logging) s.Log("does not discard their deck");
        }
    }
};

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

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

class CardRemodel : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        if(s.players[s.player].hand.Length() > 0)
        {
            s.decision.SelectCards(c, 1, 1);
            if(decisionText) s.decision.text = "Select a card to trash:";
            s.decision.cardChoices = s.players[s.player].hand;
            s.stack.PushEnd(new EventRemodelExpand(c, 2));
        }
        else
        {
            if(logging) s.Log("has no cards to trash");
        }
    }
};

class CardMine : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        if(s.players[s.player].TreasureCount() > 0)
        {
            s.decision.SelectCards(c, 1, 1);
            if(decisionText) s.decision.text = "Select a treasure to trash:";
            for(Card *c : s.players[s.player].hand)
            {
                if(c->isTreasure) s.decision.AddUniqueCard(c);
            }
            s.stack.PushEnd(new EventMine(c));
        }
        else
        {
            if(logging) s.Log("has no treasures to trash");
        }
    }
};

class CardLibrary : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        if(s.players[s.player].hand.Length() < 7)
        {
            s.stack.PushEnd(new EventLibrary(c));
        }
        else
        {
            if(logging) s.Log("already has 7 cards in hand");
        }
    }
};

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

class CardWitch : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        for(const PlayerInfo &p : s.data->players)
        {
            if(p.index != s.player)
            {
                s.stack.PushEnd(new EventGainCard(p.index, s.data->baseCards.curse, false, true, GainToDiscard));
            }
        }
    }
};

class CardGardens : public CardEffect
{
public:
    int VictoryPoints(const State &s, UINT playerIndex) const
    {
        return s.players[playerIndex].TotalCards() / 10;
    }
};

class CardBureaucrat : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.stack.PushEnd(new EventGainCard(s.player, s.data->baseCards.silver, false, false, GainToDeckTop));
        for(const PlayerInfo &p : s.data->players)
        {
            if(p.index != s.player)
            {
                s.stack.PushEnd(new EventBureaucratAttack(c, p.index));
            }
        }
    }
    bool CanProcessDecisions() const
    {
        return true;
    }
    void ProcessDecision(State &s, const DecisionResponse &response) const
    {
        Card *c = response.cards[0];
        PlayerState &p = s.players[s.decision.controllingPlayer];
        p.hand.RemoveSwap(p.hand.FindFirstIndex(c));
        p.deck.PushEnd(c);
        if(logging) s.LogIndent(1, s.decision.controllingPlayer, "puts " + c->PrettyName() + " on their deck");
    }
};

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

class CardThroneRoom : public CardEffect
{
public:
    void PlayAction(State &s) const
    {
        s.stack.PushEnd(new EventPlayActionNTimes(c, 2));
    }
};