//http://stackoverflow.com/questions/26875913/evil-hangman-implementation-in-python
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <ctime>
#include <algorithm>

const int max_guesses = 8;

std::string rawfile;
std::vector<const char*> wordlist;
std::string targetword;
std::string guessedletters;

void loadwordlist(int length);
bool filter(int length, char newguess);
void finalize();

int main() {
    srand((unsigned)time(NULL));
    int length=rand()%5+rand()%5+rand()%5+3; //random between 3 and 15, normal-ish distribution
    loadwordlist(length);
    targetword.assign(length,'?');
    int guessno = 1;
    int wrongguesses = 0;
    
    std::cout << "Welcome to Hangman!\n"
        "I have selected a word from an english dictionary.\n"
        "I will first show you the length of the secret word\n"
        "as a series of dashes.\n"
        "Your task is to guess the secret word one letter at a time.\n"
        "If you guess a correct letter I will show you the guessed\n"
        "letter(s) in the correct position.\n"
        "You can only make "<<max_guesses<<" wrong guesses before you are hanged\n"
        "\t\tGood luck\n";

    while(wrongguesses < max_guesses)  {
        char newguess = 0;
        std::cout << "Word: " << targetword;
        std::cout << "\nGuessed Letters: " << guessedletters;
        std::cout << "\nWrong Guesses: " << wrongguesses;
        std::cout << "\nGuess #" << guessno << ":";
        std::cin >> newguess;
        std::cout << '\n';

        if (newguess >= 'a' && newguess <= 'z' && guessedletters.find(newguess)==std::string::npos) {
            ++guessno;
            guessedletters += newguess;
            std::sort(guessedletters.begin(), guessedletters.end());

            if (filter(length, newguess))
                ++wrongguesses;

            if (targetword.find('?') == std::string::npos)
                break;
        }
    }
    
    const char* selection = wordlist[rand()%wordlist.size()];
    targetword.assign(selection, length);

    if (wrongguesses == max_guesses)
        std::cout << "You ran out of guesses! The word was: " << targetword;
    else {
        std::cout << "You figured out the word " << targetword << " in " << guessno << " guesses!  What a miracle!";
    }

    return 0;
}

void loadwordlist(int length) {
    std::ifstream file("hangman.txt", std::ios::binary);
    std::getline(file, rawfile, '\0'); //read the entire file
    wordlist.clear();
    wordlist.reserve(rawfile.size()/6);
    for(auto it = rawfile.begin(); it != rawfile.end();) {
        auto it2 = it+1;
        while(it2 != rawfile.end() && *it2 != '\n')
            ++it2;
        if (it2-it == length)
            wordlist.push_back(&*it);
        if (it2 == rawfile.end()) 
            return;
        it = it2+1;
    }
}

bool filter(int length, char newguess) {
    if (wordlist.size() > 1) {
        static std::vector<int> letterpositions;
        letterpositions.assign(1<<length, 0);
        for(const char* s : wordlist) {
            unsigned flag = 0;
            for(int i=0; i<length; ++i) {
                if (s[i]==newguess)
                    flag |= (1<<i);
            }
            letterpositions[flag]++;
        }
        auto maxit = std::max_element(letterpositions.begin(), letterpositions.end());
        unsigned targetflag = maxit-letterpositions.begin();

        auto lambda = [=](const char* s)->bool {
            unsigned flag = 0;
            for(int i=0; i<length; ++i) {
                if (s[i]==newguess)
                    flag |= (1<<i);
            }
            return flag != targetflag;
        };
        auto it = std::remove_if(wordlist.begin(), wordlist.end(), lambda);
        wordlist.erase(it, wordlist.end());
        
        if (targetflag) {
            for(int i=0; i<length; ++i) {
                if ((1<<i) & targetflag)
                    targetword[i] = newguess;
            }
            return false;
        }
        return true;
    }
    bool retval = true;
    for(int i=0; i<length; ++i) {
        if (wordlist[0][i] == newguess) {
            targetword[i] = newguess;
            retval = false;
        }
    }
    return retval;
}
