#ifndef __PRSONA_NETWORKING_HPP
#define __PRSONA_NETWORKING_HPP

#define USE_SERVER_STATS 1
#define USE_SSL 0

#include <mutex>
#include <condition_variable>
#include <random>
#include <string>

#include "CivetServer.h"
#include "Scalar.hpp"
#include "base.hpp"

#define MG_WEBSOCKET_OPCODE_DATACOMPLETE 0xb
#define DEFAULT_PRSONA_PORT_STR "8080"

#define TMP_FILE_SIZE 12
#define TMP_DIR "./tmp/"
#define TMP_DIR_SIZE 6

enum RequestType {
    /* SERVER PUBLIC FUNCTIONS */

    // BASIC PUBLIC SYSTEM INFO GETTERS
    PRSONA_GIVE_BGN_PUBKEY = 'a',
    PRSONA_GIVE_NUM_CLIENTS,
    PRSONA_GIVE_NUM_SERVERS,
    PRSONA_GIVE_EG_BLIND_GENERATOR,
    PRSONA_GIVE_FRESH_GENERATOR,

    // ENCRYPTED DATA GETTERS
    PRSONA_GIVE_VOTE_ROW,
    PRSONA_GIVE_VOTE_MATRIX,
    PRSONA_GIVE_CLIENT_TALLY,
    PRSONA_GIVE_SERVER_TALLY,
    PRSONA_GIVE_PSEUDONYMS,

    // PROOF COMMITMENT GETTERS
    PRSONA_GIVE_VOTE_ROW_COMMITMENT,
    PRSONA_GIVE_VOTE_MATRIX_COMMITMENT,
    PRSONA_GIVE_CLIENT_TALLY_COMMITMENT,
    PRSONA_GIVE_SERVER_TALLY_COMMITMENT,
    PRSONA_GIVE_PSEUDONYMS_COMMITMENT,

    // CLIENT INTERACTIONS
    PRSONA_RECEIVE_NEW_CLIENT,
    PRSONA_RECEIVE_VOTE,

    /* SERVER PRIVATE FUNCTIONS */

    // CLIENT INTERACTION HELPER
    PRSONA_RECEIVE_UPDATE_WITH_NEW_USER,

    // CONSTRUCTOR HELPERS
    PRSONA_GIVE_BGN_PRIVKEY,
    PRSONA_ADD_CURR_SEED_TO_GENERATOR,
    PRSONA_RECEIVE_FRESH_GENERATOR,
    PRSONA_ADD_RAND_SEED_TO_GENERATOR,
    PRSONA_RECEIVE_EG_BLIND_GENERATOR,

    // EPOCH ROUNDS
    PRSONA_PERFORM_EPOCH_BUILD_UP,
    PRSONA_PERFORM_EPOCH_BREAK_DOWN,
    PRSONA_RECEIVE_EPOCH_UPDATE,

    // DISTRIBUTED BGN
    PRSONA_GIVE_PARTIAL_DECRYPTION = '-',
    PRSONA_RECEIVE_PARTIAL_DECRYPTION = '_',
    
    /* CLIENT PUBLIC FUNCTIONS */

    // REPUTATION PROOF VERIFIER
    PRSONA_VERIFY_REPUTATION_PROOF = 'a'
};

/* SERVER PUBLIC FUNCTIONS */

// BASIC PUBLIC SYSTEM INFO GETTERS
#define REQUEST_BGN_PUBKEY_URI "/ws?a"
#define REQUEST_NUM_CLIENTS_URI "/ws?b"
#define REQUEST_NUM_SERVERS_URI "/ws?c"
#define REQUEST_EG_BLIND_GENERATOR_URI "/ws?d"
#define REQUEST_FRESH_GENERATOR_URI "/ws?e"

// ENCRYPTED DATA GETTERS
#define REQUEST_VOTE_ROW_URI "/ws?f"
#define REQUEST_VOTE_MATRIX_URI "/ws?g"
#define REQUEST_CLIENT_TALLY_URI "/ws?h"
#define REQUEST_SERVER_TALLY_URI "/ws?i"
#define REQUEST_PSEUDONYMS_URI "/ws?j"

// PROOF COMMITMENT GETTERS
#define REQUEST_VOTE_ROW_COMMITMENT_URI "/ws?k"
#define REQUEST_VOTE_MATRIX_COMMITMENT_URI "/ws?l"
#define REQUEST_CLIENT_TALLY_COMMITMENT_URI "/ws?m"
#define REQUEST_SERVER_TALLY_COMMITMENT_URI "/ws?n"
#define REQUEST_PSEUDONYMS_COMMITMENT_URI "/ws?o"

// CLIENT INTERACTIONS
#define SUBMIT_NEW_CLIENT_URI "/ws?p"
#define SUBMIT_VOTE_URI "/ws?q"

/* SERVER PRIVATE FUNCTIONS */

// CLIENT INTERACTION HELPER
#define SUBMIT_UPDATE_WITH_NEW_USER_URI "/ws?r"

// CONSTRUCTOR HELPERS
#define REQUEST_BGN_PRIVKEY_URI "/ws?s"
#define REQUEST_ADD_CURR_SEED_FOR_FRESH_GENERATOR_URI "/ws?t"
#define SUBMIT_FRESH_GENERATOR_URI "/ws?u"
#define REQUEST_ADD_RAND_SEED_FOR_EG_BLIND_GENERATOR_URI "/ws?v"
#define SUBMIT_EG_BLIND_GENERATOR_URI "/ws?w"

// EPOCH ROUNDS
#define REQUEST_EPOCH_BUILD_UP_URI "/ws?x"
#define REQUEST_EPOCH_BREAK_DOWN_URI "/ws?y"
#define SUBMIT_EPOCH_UPDATES_URI "/ws?z"

// DISTRIBUTED BGN
#define REQUEST_PARTIAL_DECRYPTION_URI "/ws?-"
#define SUBMIT_PARTIAL_DECRYPTION_URI "/ws?_"

/* CLIENT PUBLIC FUNCTIONS */

// REPUTATION PROOF VERIFIER
#define VERIFY_REPUTATION_PROOF_URI "/ws?a"

/* SYNCHRONIZATION HANDLERS */

// ORCHESTRATOR-SERVER/CLIENT SYNCHRONIZATION
#define EXIT_URI "/exit"

// INTER-SERVER SYNCHRONIZATION
#define SERVER_CHECK_IN_URI "/init"
#define UPDATE_LOCK_URI "/lock"
#define UPDATE_UNLOCK_URI "/unlock"

// SERVER-ORCHESTRATOR SYNCHRONIZATION
#define EPOCH_READY_URI "/ready"

// CLIENT-ORCHESTRATOR SYNCHRONIZATION
#define CLIENT_READY_URI "/taskdone"

// SERVER EXPERIMENT TRIGGER
#define TRIGGER_EPOCH_URI "/epoch"

// CLIENT EXPERIMENT TRIGGERS
#define TRIGGER_VOTE_URI "/vote"
#define TRIGGER_REP_URI "/rep"

// SERVER-CLIENT SYNCHRONIZATION
#define WHICH_EPOCH_URI "/which"

/* Helper types */

// Struct to help synchronization efforts across multi-threaded websocket usage
struct synchronization_tool {
    std::mutex mtx;
    std::condition_variable cv;
    size_t val, val2;
};

/* "PUBLIC" GENERIC HELPER FUNCTIONS */

// This function must be run once before any PRSONA objects are created or used
void initialize_prsona_classes();

// Makes a random temp filename to be used in receiving serialized data
char *set_temp_filename(
    std::default_random_engine& rng,
    struct mg_connection *conn);

// Loads config information
void load_multiple_instances_config(
    std::vector<std::string>& relevantIPs,
    std::vector<int>& relevantPorts,
    const char *filename);

void load_single_instance_config(
    std::string& relevantIP,
    std::string& relevantPortStr,
    int& relevantPort,
    const char *filename);

// Extracts relevant log data from server
std::vector<size_t> get_server_log_data(
    const struct mg_context *ctx);

std::vector<size_t> get_conn_log_data(
    const struct mg_context *ctx,
    bool receivedData);

// Write log data to file
void write_log_data(
    std::mutex& outputMtx,
    const std::string& outputFilename,
    const std::vector<double>& timingData,
    const std::vector<size_t>& bandwidthData);

void write_usage_data(
    std::mutex& outputMtx,
    const std::string& outputFilename);

/* "PRIVATE" FUNCTIONS TO HELP THE GENERIC HELPERS */

// Helper for set_temp_filename()
std::string random_string(
    std::default_random_engine& rng,
    size_t length);

// Helper for get_log_data()
size_t parse_log_for_data(
    const char *input,
    const char *key);

// This function gets current memory usage
unsigned long mem_usage();

/* WEBSOCKET HANDLER FUNCTIONS */

// NULL
int empty_websocket_data_handler(
    struct mg_connection *conn,
    int bits,
    char *data,
    size_t data_len,
    void *user_data);

void empty_websocket_close_handler(
    const struct mg_connection *conn,
    void *user_data);

// SYNCHRONIZATION ONLY
int synchro_websocket_data_handler(
    struct mg_connection *conn,
    int bits,
    char *data,
    size_t data_len,
    void *user_data);

void synchro_websocket_close_handler(
    const struct mg_connection *conn,
    void *user_data);

// RECEIVE SERIALIZED DATA ONLY
int file_websocket_data_handler(
    struct mg_connection *conn,
    int bits,
    char *data,
    size_t data_len,
    void *user_data);

void file_websocket_close_handler(
    const struct mg_connection *conn,
    void *user_data);

// SYNCHRONIZATION AND RECEIVE SERIALIZED DATA
int epoch_websocket_data_handler(
    struct mg_connection *conn,
    int bits,
    char *data,
    size_t data_len,
    void *user_data);

void epoch_websocket_close_handler(
    const struct mg_connection *conn,
    void *user_data);

// SPECIAL FOR HANDLING UNUSUAL DATA

int clients_websocket_data_handler(
    struct mg_connection *conn,
    int bits,
    char *data,
    size_t data_len,
    void *user_data);

/* GENERIC HANDLERS */

// Used for signaling exits
class RemoteControlHandler : public CivetHandler
{
    public:
        RemoteControlHandler(
            struct synchronization_tool *sync);

        RemoteControlHandler(
            struct synchronization_tool *sync,
            const std::string& message);

        bool handleGet(
            CivetServer *server,
            struct mg_connection *conn);

    private:
        struct synchronization_tool *sync;
        const std::string message;
};

// Used for signaling events (making votes, epoch changes, etc.)
class AltRemoteControlHandler : public CivetHandler
{
    public:
        AltRemoteControlHandler(
            size_t value,
            struct synchronization_tool *sync);

        AltRemoteControlHandler(size_t value,
            struct synchronization_tool *sync,
            const std::string& message);

        bool handleGet(CivetServer *server, struct mg_connection *conn);

        std::string getQuery() const;

    private:
        const size_t value;
        struct synchronization_tool *sync;
        const std::string message;
        std::string query;
};

#endif
