#include "filesystem.h"


#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>

#include <sys/stat.h>
#include <stdio.h>
#include <stdbool.h>
#include <formatters.h>
#include <unistd.h>
#include <stdlib.h>

// =====================================================================================================================
// time for waiting between file stats
static const std::chrono::milliseconds sk_waitTime = std::chrono::milliseconds(100);

// Check if a file exists
bool FileExists( const std::string& name )
{
    struct stat buffer;
    return (stat (name.c_str(), &buffer) == 0);
}

// Wait for a file to exist
void WaitForFile( const std::string& name, const std::chrono::milliseconds& time )
{
    while( !FileExists(name) ) std::this_thread::sleep_for(time);
}

bool OutputGameStateFile(CTTRTSGame &game, const std::string &gameDir)
{
    char turnFileName[128];
    snprintf(turnFileName,128,"%s/Turn_%i.txt",gameDir.c_str(),game.GetTurn());
    std::ofstream turnFile(turnFileName, std::ios_base::trunc); // truncate to overwrite if a file exists

    if ( turnFile.bad() )
    {
        return false;
    }

    // Output the turn description
    std::string turnDescriptor = GetStringFromGame(game);

    turnFile<<turnDescriptor;
    turnFile.close();

    return true;
}

std::string getMapsDir()
{
    std::string maps = STRINGIFY(TTRTS_MAPS);
    if( getenv("TTRTS_MAPS") )
    {
        maps = getenv("TTRTS_MAPS");

        // Additional trailing slash
        if( maps.back() != '/' )
            maps += "/";
    }

    return maps;
}

std::string getGamesDir()
{
    std::string dir = STRINGIFY(TTRTS_GAMES);
    if( getenv("TTRTS_GAMES") )
    {
        dir = getenv("TTRTS_GAMES");

        // Additional trailing slash
        if( dir.back() != '/' )
            dir += "/";
    }
    return dir;
}

CTTRTSGame GetGameFromFile( const std::string& filename )
{
    std::string gamefile = filename;

    // Default for maps
    std::string ttrts_maps_dir = getMapsDir();

    // If file path is not local path and file doesn't exist
    if( gamefile.find("/") == std::string::npos
            && access( gamefile.c_str(), F_OK ) == -1 )
    {
        gamefile = ttrts_maps_dir + gamefile;
    }

    // If still not good
    if( access( gamefile.c_str(), F_OK ) == -1 )
    {
        std::cerr<<"Error: "<< gamefile <<" file not found"<<std::endl;
        return CTTRTSGame(0,0);
    }

    std::ifstream file(gamefile);

    std::string gameDescriptor;

    // Reserve the string needed up front
    file.seekg(0, std::ios::end);
    gameDescriptor.reserve(file.tellg());
    file.seekg(0, std::ios::beg);

    // Grab the string from the file
    gameDescriptor.assign((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());

    if( gameDescriptor.size() == 0 )
    {
        std::cerr<<"Error: failed to read in any information from "<<gamefile<<std::endl;
        return CTTRTSGame(0,0);
    }

    // Create the game
    return GetGameFromString(gameDescriptor);
}

// =====================================================================================================================
int runFromFilesystem(int argc, char* argv[])
{
    std::string gamefile = argv[1];

    std::cout<<"Launching TTRTS with "<<gamefile<<std::endl;
    CTTRTSGame game = GetGameFromFile(gamefile);

    // Grab the players involved
    auto players = game.GetPlayers();

    // Default for games
    std::string ttrts_games_dir = getGamesDir();

    // Current game directory
    std::string gameDir = ttrts_games_dir + game.GetName();

    // Empty the current game directory
    struct stat info;
    int ret = stat( gameDir.c_str(), &info );
    if( ret == 0 && info.st_mode & S_IFDIR )
    {
        std::cout<< gameDir << " game directory already exists"<<std::endl;
        std::cout<<"Confirm to delete contents [y/N] ";
        std::string input;
        std::cin>>input;
        if( !input.size() || std::tolower(input[0]) != 'y' )
        {
            std::cerr<<"Aborting..."<<std::endl;
            return -1;
        }
    }
    else if ( ret  == 0 )
    {
        std::cerr<< gameDir << " exists but is not directory \nAborting..."<<std::endl;
        return -1;
    }

    // Create the game directory
    char cmd2[128];
    snprintf(cmd2,128, "test -d %s || mkdir %s",gameDir.c_str(),gameDir.c_str());
    if( system(cmd2) == -1)
    {
        std::cerr<<"Error: Failed to create the game directory"<<std::endl;
        return -1;
    }

    // Clean out the game directory
    char cmd1[128];
    snprintf(cmd1,128, "rm -rf %s/*",gameDir.c_str());
    if ( system(cmd1) == -1 )
    {
        std::cerr<<"Error: Failed to clean the game directory"<<std::endl;
        return -1;
    }

    // While the game isn't finished
    while ( ! game.GameOver() )
    {
        std::cout<<"Starting turn "<<game.GetTurn()<<std::endl;

        // Create a turn file
        if( !OutputGameStateFile(game, gameDir))
        {
            std::cerr<<"Error: Failed to output new turn file" << std::endl;
            return 1;
        }

        // Wait for order files
        for( player_t player : players)
        {
            // Construct the player order filename
            char playerOrderFileName[128];
            snprintf(playerOrderFileName, 128, "%s/Player_%i_Turn_%i.txt", gameDir.c_str(), (int) player, game.GetTurn());

            // Wait for the player order file to be created
            std::cout<<"Waiting for "<< playerOrderFileName <<std::endl;
            bool hasOrderFile = false;
            while(!hasOrderFile)
            {
                WaitForFile(playerOrderFileName,sk_waitTime); // Wait for the file

                // File must have END
                // Method taken from http://stackoverflow.com/questions/11876290/c-fastest-way-to-read-only-last-line-of-text-file
                std::ifstream turnFile(playerOrderFileName);
                turnFile.seekg(-1,std::ios_base::end);

                // Loop back from the end of file
                bool keepLooping = true;
                while(keepLooping) {
                    char ch;
                    turnFile.get(ch);                            // Get current byte's data

                    if((int)turnFile.tellg() <= 1) {             // If the data was at or before the 0th byte
                        turnFile.seekg(0);                       // The first line is the last line
                        keepLooping = false;                // So stop there
                    }
                    else if(ch == '\n') {                   // If the data was a newline
                        keepLooping = false;                // Stop at the current position.
                    }
                    else {                                  // If the data was neither a newline nor at the 0 byte
                        turnFile.seekg(-2,std::ios_base::cur);        // Move to the front of that data, then to the front of the data before it
                    }
                }

                // Grab this line
                std::string lastLine;
                std::getline(turnFile,lastLine);
                if(lastLine == "END")
                    hasOrderFile = true;
            }

            std::ifstream turnFile(playerOrderFileName);

            // Reserve the full order string
            std::string orders;
            turnFile.seekg(0, std::ios::end);
            orders.reserve(turnFile.tellg());
            turnFile.seekg(0, std::ios::beg);

            // Grab the string from the file
            orders.assign((std::istreambuf_iterator<char>(turnFile)),std::istreambuf_iterator<char>());

            // Issue the orders to the game
            if( game.IssueOrders(player, orders) )
                std::cerr<<"Warning: Orders for player "<<(int) player <<" failed to correctly parse"<<std::endl;
        }

        // Simulate turn
        std::cout<<"Simulating this turn!"<<std::endl;
        if ( game.SimulateToNextTurn() )
        {
            std::cerr << "Error: Failed to simulate for turn "<<game.GetTurn()<<std::endl;
            return -1;
        }

    }

    // Output final gamestate
    OutputGameStateFile(game, gameDir);

    // Get the winning player
    player_t winningPlayer = game.GetWinningPlayer();

    // Print the winner!
    if ( winningPlayer != player_t::NUM_INVALID )
    {
        std::cout<<"Game over! Winner:"<<(int) winningPlayer <<std::endl;
    }
    else
    {
        std::cout<<"Game over! It was a draw!"<<std::endl;
    }

    return (int)winningPlayer;
}