#include <string>
#include <array>
#include <algorithm>
#include <ostream>
#include <iostream>

#include <random>

namespace constants
{
static constexpr const size_t num_values = 13;
static constexpr const size_t num_suits  = 4;
static constexpr const size_t num_cards  = num_values * num_suits;
}

enum class Value : int8_t
{
    None = -1,
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Nine,
    Ten,
    Jack,
    Queen,
    King,
    Ace
};

    enum class Suit : int8_t
    {
        None = -1,
        Heart,
        Diamond,
        Spade,
        Club
    };


    template<typename T>
    std::string to_string(const T&);

    template<>
    std::string to_string<Value>(const Value& value)
    {
        std::string str;
        switch(value)
        {
        case Value::None:  str = "None"; break;
        case Value::Two:   str = "2"; break;
        case Value::Three: str = "3"; break;
        case Value::Four:  str = "4"; break;
        case Value::Five:  str = "5"; break;
        case Value::Six:   str = "6"; break;
        case Value::Seven: str = "7"; break;
        case Value::Eight: str = "8"; break;
        case Value::Nine:  str = "9"; break;
        case Value::Ten:   str = "T"; break;
        case Value::Jack:  str = "J"; break;
        case Value::Queen: str = "Q"; break;
        case Value::King:  str = "K"; break;
        case Value::Ace:   str = "A"; break;

        default: break;
        }
        return str;
    }

    template<>
    std::string to_string<Suit>(const Suit& suit)
    {
        std::string str;
        switch(suit)
        {
        case Suit::None:    str = "None"; break;
        case Suit::Heart:   str = "h"; break;
        case Suit::Diamond: str = "d"; break;
        case Suit::Spade:   str = "s"; break;
        case Suit::Club:    str = "c"; break;
        default: break;
        }

        return str;
    }

    class Card
    {
    public:

        constexpr Card() = default;
        constexpr Card(Value value, Suit suit) : m_value(value), m_suit(suit) {}

        Value  value()  const noexcept { return m_value; }
        Suit   suit()   const noexcept { return m_suit;  }

        Card& operator++()
        {
            if(m_value == Value::Ace)
                m_suit  = static_cast<Suit>((static_cast<int8_t>(m_suit) + 1) % constants::num_suits);

            m_value = static_cast<Value>((static_cast<int8_t>(m_value) + 1) % constants::num_values);
            return *this;
        }
        Card  operator++(int)
        {
            Card result(*this);
            ++(*this);
            return result;
        }

    private:
        Value m_value{};
        Suit  m_suit{};

    };


std::ostream& operator<<(std::ostream& os, const Card& card)
{
    os << to_string(card.value()) << to_string(card.suit());
    return os;
}

class Deck
{
public:
    using Type           = std::array<Card, constants::num_cards>;
    using iterator       = Type::iterator;
    using const_iterator = Type::const_iterator;

    iterator       begin()       noexcept { return m_deck.begin(); }
    const_iterator begin() const noexcept { return m_deck.begin(); }
    iterator       end()         noexcept { return m_deck.end();   }
    const_iterator end()   const noexcept { return m_deck.end();   }

    void init()
    {
        std::iota(m_deck.begin(), m_deck.end(), Card(Value::Two, Suit::Heart));
    }

    void randomInit()
    {
        init();
        std::shuffle(m_deck.begin(), m_deck.end(), m_g);
    }

private:

    Type m_deck;
    std::random_device m_rd;
    std::mt19937 m_g{m_rd()};
};

std::ostream& operator<<(std::ostream& os, const Deck& deck)
{
    for(const auto& card : deck)
        os << card << ' ';

    return os;
}

int main(int argc, char** argv)
{
    Deck deck;

    deck.randomInit();
    std::cout << deck << '\n';

    return 0;
}
