#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include <functional>

using namespace std;

class Guess
{
public:
    const std::string& GetGuess() const
    {
        return(m_guess);
    }

    static Guess TakeGuess()
    {
        Guess g;
        std::cin >> g.m_guess;
        return(std::move(g));
    }

private:
    std::string m_guess;
};

template<char L>
class Letter
{
public:
    Letter():
        m_char(L)
    {
    }

    char GetChar() const
    {
        return(m_char);
    }

    bool operator==(const Letter<L>& other) const
    {
        return(other.m_char == m_char);
    }

protected:
    const char m_char;
};

class MutableLetter: public Letter<0>
{
public:
    MutableLetter():
        Letter()
    {
    }

    void SetChar(char c)
    {
        const_cast<char&>(m_char) = c;
    }
};

class Word
{
public:
    Word()
    {
    }

    template<char L>
    void AppendLetter(const Letter<L>& letter)
    {
        m_letters.emplace_back();
        reinterpret_cast<MutableLetter&>(m_letters.back()).SetChar(letter.GetChar());
    }

    template<char L>
    std::vector<size_t> HasLetter(const Letter<L>& letter) const
    {
        std::vector<size_t> indices;
        for(size_t i = 0; i < m_letters.size(); ++i)
        {
            if(m_letters[i] == static_cast<const Letter<0>&>(letter))
            {
                indices.emplace_back(i);
            }
        }
        return(indices);
    }

    size_t Count() const
    {
        return(m_letters.size());
    }

    template<char L>
    void SetLetter(size_t index, const Letter<L>& letter)
    {
        static_cast<MutableLetter&>(m_letters[index]).SetChar(letter.GetChar());
    }

    bool MissingLetter(size_t index) const
    {
        return(m_letters[index].GetChar() == '_');
    }

private:
    std::vector<Letter<0>> m_letters;

    friend class WordPrinter;
};

class WordPrinter
{
public:
    WordPrinter(const Word& word):
        m_word(word)
    {
    }

    void PrintWord() const
    {
        for(const Letter<0>& letter : m_word.m_letters)
        {
            // const correctness!
            const char temp[2] = { letter.GetChar(), '\0' };
            cout << temp;
        }
        cout << endl;
    }

private:
    const Word& m_word;
};

class GuessTracker
{
public:
    GuessTracker(size_t numGuesses):
        m_numGuesses(numGuesses)
    {
    }

    bool RunGuessLoop(std::function<bool()> guessFn)
    {
        bool result = true;
        if(m_numGuesses > 0)
        {
            while(m_numGuesses --> 0)
            {
                result = guessFn();
                if(result || m_numGuesses == 0)
                {
                    break;
                }
            }
        }
        return(result);
    }

private:
    size_t m_numGuesses;
};

class HangmanGame
{
public:
    HangmanGame(size_t numGuesses, std::string secret):
        m_guessTracker(numGuesses),
        m_secret()
    {
        for(char c : secret)
        {
            Letter<0> letter;
            reinterpret_cast<MutableLetter&>(letter).SetChar(c);
            m_secret.AppendLetter(letter);

            // Reuse letter for efficiency
            reinterpret_cast<MutableLetter&>(letter).SetChar('_');
            m_answer.AppendLetter(letter);
        }
    }

    int Run()
    {
        WordPrinter printer(m_answer);
        size_t lettersRemaining = m_secret.Count();
        const bool result = m_guessTracker.RunGuessLoop([&]()
        {
            cout << "Enter a letter. ";
            printer.PrintWord();
            const Guess guess = Guess::TakeGuess();
            MutableLetter guessLetter;
            guessLetter.SetChar(guess.GetGuess().front());

            size_t occurrences = 0;
            const auto indices = m_secret.HasLetter(guessLetter);

            for (size_t ind : indices)
            {
                if(m_answer.MissingLetter(ind))
                {
                    m_answer.SetLetter(ind, guessLetter);
                    occurrences += 1;
                }
            }
            if (occurrences == 0)
            {
                // bummer
                cout << "Letter not found, try again" << endl;
            }

            lettersRemaining -= occurrences;
            if (lettersRemaining == 0)
            {
                return(true);
            }

            return(false);
        });
        if(result)
        {
            cout << "Winner" << endl;
        }
        else
        {
            cout << "Loser" << endl;
        }

        WordPrinter secretPrinter(m_secret);
        cout << "The word was ";
        secretPrinter.PrintWord();
        return(0);
    }

private:
    GuessTracker m_guessTracker;
    Word m_secret;
    Word m_answer;
};

int main()
{
    HangmanGame game(7, "hi");
    return(game.Run());
}
