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

#include <iostream>
#include <fstream>

#include "networkServer.hpp"

using namespace std;

/**
 * This program (bin/server) expects to be called as follows:
 * `bin/server <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 server 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 epochBuildUpOutputMtx;
    string epochBuildUpOutputFilename = outputDir + "/epochUp.out";
    mutex epochBreakDownOutputMtx;
    string epochBreakDownOutputFilename = outputDir + "/epochDown.out";
    mutex epochUpdateOutputMtx;
    string epochUpdateOutputFilename = outputDir + "/epochUpdate.out";
    mutex voteUpdateOutputMtx;
    string voteUpdateOutputFilename = outputDir + "/voteUpdate.out";
    mutex fullEpochOutputMtx;
    string fullEpochOutputFilename = outputDir + "/overallEpoch.out";
    mutex memUsageOutputMtx;
    string memUsageOutputFilename = outputDir + "/usage.out";

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

    // Set lambda where necessary
    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.
    string seedStr;
    if (id.empty())
        seedStr = "default-server";
    else
    {
        seedStr = id;
        seedStr += "-" + output;
        seedStr += "-server";
    }
    seed_seq seed(seedStr.begin(), seedStr.end());
    default_random_engine rng(seed);

    vector<string> serverIPs;
    vector<int> serverPorts;
    string selfIP, selfPortStr, dealerIP, dealerPortStr;
    int selfPort = 0, dealerPort = 0;

    string configDir = "cfg/" + output;

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

    // 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());

    // And finally the dealer location
    load_single_instance_config(dealerIP, dealerPortStr, dealerPort, (configDir + "/dealerIP.cfg").c_str());

    size_t numServers = serverIPs.size();
    bool bgnDealer = selfIP == dealerIP && selfPort == dealerPort;
    struct synchronization_tool exitSync, readySync;
    mutex updateMtx;
    unique_lock<mutex> *updateLockHolder = NULL;
    atomic<size_t> epochNum(0);
    const char *options[] = {"listening_ports", selfPortStr.c_str(), 0};
    CivetServer server(options);

    /*
     * SERVER SETUP CODE
     */

    cout << "[" << seedStr << "] Establishing PRSONA server with the following parameters: " << endl;
    cout << "[" << seedStr << "] " << numServers << " PRSONA servers" << endl;
    cout << "[" << seedStr << "] This server " << (bgnDealer ? "IS" : "is NOT") << " the trusted BGN dealer" << 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 server is at IP address: " << selfIP << ":" << selfPort << endl;
    cout << "[" << seedStr << "] The BGN dealer is at IP address: " << dealerIP << ":" << dealerPort << endl;
    cout << endl;

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

    PrsonaServer *prsonaServer = create_server(rng, dealerIP, dealerPort, bgnDealer, numServers);

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

    // Main handler
    PrsonaServerWebSocketHandler wsHandler(rng, prsonaServer, serverIPs, serverPorts, selfIP, selfPort, updateMtx, epochNum, epochBuildUpOutputMtx, epochBuildUpOutputFilename, epochBreakDownOutputMtx, epochBreakDownOutputFilename, epochUpdateOutputMtx, epochUpdateOutputFilename, voteUpdateOutputMtx, voteUpdateOutputFilename, memUsageOutputMtx, memUsageOutputFilename);
    server.addWebSocketHandler("/ws", wsHandler);

    // Exit handler (allows server to be brought down by making correct GET request)
    unique_lock<mutex> exitLock(exitSync.mtx);
    exitSync.val = 0;
    exitSync.val2 = 0;
    RemoteControlHandler exitHandler(&exitSync, "Server coming down!");
    server.addHandler(EXIT_URI, exitHandler);

    // Update mutex handler (allows servers to synchronize updates, like epochs and new users)
    UpdateLockWebSocketHandler lockHandler(updateMtx, &updateLockHolder, true);
    UpdateLockWebSocketHandler unlockHandler(updateMtx, &updateLockHolder, false);
    server.addWebSocketHandler(UPDATE_LOCK_URI, lockHandler);
    server.addWebSocketHandler(UPDATE_UNLOCK_URI, unlockHandler);

    // Epoch number handler (allows a server to indicate what epoch the system is on)
    EpochNumHandler epochNumHandler(epochNum);
    server.addHandler(WHICH_EPOCH_URI, epochNumHandler);

    // This handler (to determine system main loop readiness) is only used by the dealer
    RemoteControlHandler serverReadyHandler(&readySync, "ACK");
    // Similarly, this handler (to tell the orchestrator when epochs are ready to happen) is only used by the dealer
    EpochReadyHandler epochReadyHandler(&exitSync, &readySync, updateMtx, numServers);
    // Make-epoch trigger (which causes the dealer to initiate an epoch change)
    AltRemoteControlHandler triggerEpochHandler(1, &exitSync, "Server will initiate epoch!");
    // It's probably best to give them all a lifetime as long as the CivetWeb server object

    if (bgnDealer)      // The dealer needs to wait for the other servers to check in, because it needs to lead forming the first fresh generator
    {
        // The readySync lock is only used here, while we're waiting for other servers to check in
        unique_lock<mutex> lck(readySync.mtx);
        readySync.val = 1;
        server.addHandler(SERVER_CHECK_IN_URI, serverReadyHandler);

        // In addition, there are also a few extra handlers only the dealer needs
        server.addHandler(TRIGGER_EPOCH_URI, triggerEpochHandler);
        server.addHandler(EPOCH_READY_URI, epochReadyHandler);

        cout << "[" << seedStr << "] Waiting for other servers to retrieve BGN details and check in." << endl;

        // Wait for the other servers to check in
        while (readySync.val < numServers)
            readySync.cv.wait(lck);

        // Once we're ready, form and distribute the first fresh generator and the H used in ElGamal operations
        initiate_generators(rng, prsonaServer, serverIPs, serverPorts, selfIP, selfPort);
    }
    else            // Non-dealer servers need to check in with the dealer
    {
        cout << "[" << seedStr << "] Checking in with the BGN dealer." << endl;

        check_in_with_dealer(dealerIP, dealerPort);
    }

    /*
     * MAIN SERVER LOOP CODE
     */

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

    if (bgnDealer)
    {
        while (!exitSync.val)
        {
            // exitSync.val controls when an exit is signaled.
            // exitSync.val2 controls when an epoch change is signaled
            while (!exitSync.val && !exitSync.val2)
                exitSync.cv.wait(exitLock);

            // exitSync.val2 is set by the make-epoch handler, and will be 0 in the event of signaling an exit
            if (exitSync.val2)
            {
                size_t currEpoch = epochNum.load();
                cout << "[" << seedStr << "] Executing epoch calculations (going from t = " << currEpoch << " to " << currEpoch + 1 << ")." << endl;
                make_epoch(rng, prsonaServer, serverIPs, serverPorts, selfIP, selfPort, updateMtx, epochNum, server, epochBuildUpOutputMtx, epochBuildUpOutputFilename, epochBreakDownOutputMtx, epochBreakDownOutputFilename, fullEpochOutputMtx, fullEpochOutputFilename, memUsageOutputMtx, memUsageOutputFilename);
                currEpoch = epochNum.load();
                cout << "[" << seedStr << "] Epoch calculations complete (now in t = " << currEpoch << ")." << endl;
            }

            // Make sure to reset this, so that we wait until something new is signaled to do
            exitSync.val2 = 0;
        }
    }
    else
    {
        // exitSync.val controls when an exit is signaled.
        // exitSync.val2 is not used by non-dealer servers.
        while (!exitSync.val)
            exitSync.cv.wait(exitLock);
    }

    /*
     * SHUTDOWN CODE
     */

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

    mg_exit_library();
    delete prsonaServer;

    return 0;
}
