/**
 * clientMain.cpp
 *      - compiles to bin/client
 *      - runs a client program for experiments with PRSONA
 *
 * Stan Gurtler
 */

#include <iostream>
#include <fstream>

#include "networkClient.hpp"

using namespace std;

/**
 * This program (bin/client) expects to be called as follows:
 * `bin/client <id> <output> <servers_are_malicious>`
 *
 * <id> - a name for this instance, which comes with a config file identifying
 *      its IP address and port
 * <output> - a string that will name the file in which outputs for this run of
 *      the program will be written (that is, timings and traffic data)
 * <lambda> - a positive integer that determines the absolute soundness parameter
 *      for batched proofs
 * <servers_are_malicious> - a bool (given as T/t or F/f)
 *      which is true when servers are in malicious security
 *      and false when they are in HBC security
 */
int main(int argc, char *argv[])
{
    /*
     * PRELIMINARY SETUP CODE
     */

    initialize_prsona_classes();
#if USE_SSL
    mg_init_library(MG_FEATURES_SSL);
#else
    mg_init_library(0);
#endif

    // The id effectively tells the client what ip/port it is
    string id = "";
    if (argc > 1)
        id = argv[1];

    string output = "default";
    if (argc > 2)
        output = argv[2];

    string outputDir = "out/" + output + "/" + id;
    int mkdirOut = system(("mkdir -p " + outputDir).c_str());

    mutex voteOutputMtx;
    string voteOutputFilename = outputDir + "/vote.out";
    mutex repProofOutputMtx;
    string repProofOutputFilename = outputDir + "/repProver.out";
    mutex repVerifyOutputMtx;
    string repVerifyOutputFilename = outputDir + "/repVerifier.out";
    mutex memUsageOutputMtx;
    string memUsageOutputFilename = outputDir + "/usage.out";

    // Default to not doing proof batching if not specified
    size_t lambda = 0;
    if (argc > 3)
        lambda = atoi(argv[3]);

    if (lambda > 0)
        PrsonaBase::set_lambda(lambda);

    // Default to malicious security if not specified
    bool maliciousServers = true;
    if (argc > 4)
        maliciousServers = argv[4][0] == 't' || argv[4][0] == 'T';

    // Set malicious flags where necessary
    if (maliciousServers)
        PrsonaBase::set_server_malicious();

    // This seed is used to generate temp filenames and decide which server to make requests to at any step.
    string seedStr;
    if (id.empty())
        seedStr = "default-client";
    else
    {
        seedStr = id;
        seedStr += "-" + output;
        seedStr += "-client";
    }
    seed_seq seed(seedStr.begin(), seedStr.end());
    default_random_engine rng(seed);
    
    vector<string> serverIPs, clientIPs;
    vector<int> serverPorts, clientPorts;
    string selfIP, selfPortStr;
    int selfPort = 0;

    string configDir = "cfg/" + output;

    // Read in from config files the server locations
    load_multiple_instances_config(serverIPs, serverPorts, (configDir + "/serverIPs.cfg").c_str());

    // And now the client locations
    load_multiple_instances_config(clientIPs, clientPorts, (configDir + "/clientIPs.cfg").c_str());

    // Finally, read in the ip/port corresponding to the id that this instance was given
    string selfConfigFilename = configDir + "/selfIP";
    if (!id.empty())
    {
        selfConfigFilename += "-";
        selfConfigFilename += id;
    }
    selfConfigFilename += ".cfg";
    load_single_instance_config(selfIP, selfPortStr, selfPort, selfConfigFilename.c_str());

    size_t numServers = serverIPs.size();
    size_t numClients = clientIPs.size();
    uniform_int_distribution<size_t> distribution(0, numServers - 1);
    const char *options[] = {"listening_ports", selfPortStr.c_str(), 0};
    CivetServer server(options);

    /*
     * CLIENT SETUP CODE
     */

    struct synchronization_tool exitSync;
    unique_lock<mutex> exitLock(exitSync.mtx);

    cout << "[" << seedStr << "] Establishing PRSONA client with the following parameters: " << endl;
    cout << "[" << seedStr << "] " << numServers << " PRSONA servers" << endl;
    cout << "[" << seedStr << "] " << numClients << " PRSONA clients" << endl;
    cout << "[" << seedStr << "] " << "Proof batching " << (lambda > 0 ? "IS" : "is NOT") << " in use." << endl;
    if (lambda > 0)
        cout << "[" << seedStr << "] Batch parameter: " << lambda << endl;
    cout << "[" << seedStr << "] Servers are set to " << (maliciousServers ? "MALICIOUS" : "HBC") << " security" << endl;
    cout << "[" << seedStr << "] This client is at IP address: " << selfIP << ":" << selfPort << endl;
    cout << endl;

    cout << "[" << seedStr << "] Creating PRSONA client object." << endl;

    PrsonaClient *prsonaClient = create_client(rng, serverIPs, serverPorts, numServers);

    cout << "[" << seedStr << "] Setting up handlers for client." << endl;

    // Main handler (in clients, only used for verifying reputation proofs)
    PrsonaClientWebSocketHandler wsHandler(rng, prsonaClient, serverIPs, serverPorts, repVerifyOutputMtx, repVerifyOutputFilename, memUsageOutputMtx, memUsageOutputFilename);
    server.addWebSocketHandler("/ws", wsHandler);

    // Exit handler (allows client to be brought down by making correct GET request)
    exitSync.val = 0;
    exitSync.val2 = 0;
    RemoteControlHandler exitHandler(&exitSync, "Client coming down!");
    server.addHandler(EXIT_URI, exitHandler);

    // This handler tells the orchestrator when the client has finished an assigned task
    ClientReadyHandler clientReadyHandler(&exitSync);
    server.addHandler(CLIENT_READY_URI, clientReadyHandler);

    // Make-vote handler (allows orchestrator to cause this client to make a new, random vote)
    AltRemoteControlHandler triggerVoteHandler(CLIENT_MAKE_VOTE, &exitSync, "Client will make new votes!");
    server.addHandler(TRIGGER_VOTE_URI, triggerVoteHandler);

    // Make-reputation handler (allows orchestrator to cause this client to make a new reputation proof, and specifies which other client to give it to)
    AltRemoteControlHandler triggerRepHandler(CLIENT_MAKE_REP_PROOF, &exitSync, "Client will make reputation proof!");
    server.addHandler(TRIGGER_REP_URI, triggerRepHandler);

    /*
     * MAIN CLIENT LOOP CODE
     */

    cout << "[" << seedStr << "] Entering main ready loop." << endl;

    while (!exitSync.val)
    {
        // exitSync.val controls when an exit is signaled.
        // exitSync.val2 controls when making a vote or making a reputation proof is signaled
        while (!exitSync.val && !exitSync.val2)
            exitSync.cv.wait(exitLock);

        size_t whichServer;
        string fullQuery, target;
        size_t colonLocation;
        int targetPort;

        // exitSync.val2 is set by the make-vote and make-reputation handlers, but will be 0 in the event of signaling an exit
        switch (exitSync.val2)
        {
            case CLIENT_MAKE_VOTE:
                cout << "[" << seedStr << "] Making new vote row." << endl;
                whichServer = distribution(rng);
                make_vote(rng, prsonaClient, serverIPs, serverPorts, serverIPs[whichServer], serverPorts[whichServer], numClients, server, voteOutputMtx, voteOutputFilename, memUsageOutputMtx, memUsageOutputFilename);
                cout << "[" << seedStr << "] New vote row complete." << endl;
                break;

            case CLIENT_MAKE_REP_PROOF:
                fullQuery = triggerRepHandler.getQuery();
                colonLocation = fullQuery.find(":");

                target = fullQuery.substr(0, colonLocation);
                targetPort = stoi(fullQuery.substr(colonLocation + 1));

                cout << "[" << seedStr << "] Making new reputation proof." << endl;
                make_reputation_proof(rng, prsonaClient, serverIPs, serverPorts, target, targetPort, numClients, server, repProofOutputMtx, repProofOutputFilename, memUsageOutputMtx, memUsageOutputFilename);
                cout << "[" << seedStr << "] New reputation proof complete." << endl;
                break;

            default:    // Occurs when an exit is signaled; do nothing
                break;
        }

        // Make sure to reset this, so that we wait until something new is signaled to do
        exitSync.val2 = 0;
    }

    /*
     * SHUTDOWN CODE
     */

    cout << "[" << seedStr << "] Shutting down." << endl;

    mg_exit_library();
    delete prsonaClient;

    return 0;
}
