#ifndef __PRSONA_NETWORK_CLIENT_HPP
#define __PRSONA_NETWORK_CLIENT_HPP

#include <string>
#include <vector>
#include <random>

#include "client.hpp"
#include "networking.hpp"

enum EventType {
    CLIENT_MAKE_VOTE = 1,
    CLIENT_MAKE_REP_PROOF
};

/* "PUBLIC" FUNCTIONS */

// CREATOR FOR A NEW CLIENT
PrsonaClient *create_client(
    std::default_random_engine& rng,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    size_t numServers);

// FUNCTIONS TO PERFORM OPERATIONS FOR EXPERIMENT
void make_vote(
    std::default_random_engine& rng,
    PrsonaClient* prsonaClient,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    const std::string& target,
    int targetPort,
    size_t numClients,
    const CivetServer& civetServer,
    std::mutex& outputMtx,
    const std::string& outputFilename,
    std::mutex& usageMtx,
    const std::string& usageFilename);

bool make_reputation_proof(
    std::default_random_engine& rng,
    PrsonaClient* prsonaClient,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    const std::string& target,
    int targetPort,
    size_t numClients,
    const CivetServer& civetServer,
    std::mutex& outputMtx,
    const std::string& outputFilename,
    std::mutex& usageMtx,
    const std::string& usageFilename);

/* "PRIVATE" FUNCTIONS */

// HELPERS TO ADD THIS CLIENT TO SERVERS
void register_new_client(
    std::default_random_engine& rng,
    PrsonaClient *newUser,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    const Proof& proofOfValidSTPK,
    const Twistpoint& shortTermPublicKey);

void verify_valid_addition(
    std::default_random_engine& rng,
    PrsonaClient *newUser,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    const std::vector<Proof>& proofOfValidAddition,
    const Twistpoint& shortTermPublicKey);

// GETTERS FOR VARIOUS SERVER VALUES
Twistpoint get_generator(
    std::default_random_engine& rng,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    bool fresh,
    std::vector<Proof>& pi,
    std::vector<size_t>& bandwidthData);

BGNPublicKey get_bgn_public_key(
    std::default_random_engine& rng,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts);

template <typename T>
T get_server_committed_val(
    std::default_random_engine& rng,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    const char *firstUri,
    const char *commitUri,
    std::vector<Proof>& pi,
    const Twistpoint& shortTermPublicKey,
    std::vector<size_t>& bandwidthData);

// HELPERS FOR GENERALIZED GETTER FUNCTION
template <typename T>
T get_first_committed_val(
    std::default_random_engine& rng,
    const std::string& serverIP,
    int serverPort,
    const char *firstUri,
    Proof& pi,
    const Twistpoint& shortTermPublicKey,
    std::vector<size_t>& bandwidthData);

void get_additional_commitment(
    std::default_random_engine& rng,
    const std::vector<std::string>& serverIPs,
    const std::vector<int>& serverPorts,
    const std::string& skipIP,
    int skipPort,
    const char *commitUri,
    std::vector<Proof>& pi,
    const Twistpoint& shortTermPublicKey,
    std::vector<size_t>& bandwidthData);

// FILE I/O HELPERS FOR ALL GETTERS
std::vector<Proof> get_valid_addition_proof_from_file(
    const char *filename);

Twistpoint get_generator_from_file(
    const char *filename,
    std::vector<Proof>& pi);

BGNPublicKey get_bgn_public_key_from_file(
    const char *filename);

template <typename T>
T get_committed_val_from_file(
    const char *filename,
    Proof& pi);

Proof get_commitment_from_file(
    struct synchronization_tool *sync,
    const char *filename);

// GENERALIZED SENDER FOR ORCHESTRATOR-SIGNALED OPERATIONS
char *send_item(
    std::default_random_engine& rng,
    const std::string& target,
    int targetPort,
    const char* whichUri,
    const std::string& data,
    bool responseExpected,
    std::vector<size_t>& bandwidthData);

// DATA SERIALIZERS
std::string make_vote_string(
    const std::vector<Proof>& pi,
    const std::vector<TwistBipoint>& newVotes,
    const Twistpoint& shortTermPublicKey);

std::string make_rep_proof_string(
    const std::vector<Proof>& pi,
    const Twistpoint& shortTermPublicKey,
    const Scalar& threshold);

/* OTHER CLIENT-RELEVANT HANDLERS */

// Used to tell orchestrator when the client is no longer doing a triggered task
class ClientReadyHandler : public CivetHandler
{
    public:
        ClientReadyHandler(
            struct synchronization_tool *exitSync);

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

    private:
        struct synchronization_tool *exitSync;
};

/* CLIENT-SPECIFIC HANDLER */

class PrsonaClientWebSocketHandler : public CivetWebSocketHandler {
    public:
        // CONSTRUCTOR
        PrsonaClientWebSocketHandler(
            std::default_random_engine& rng,
            PrsonaClient *prsonaClient,
            const std::vector<std::string>& serverIPs,
            const std::vector<int>& serverPorts,
            std::mutex& outputMtx,
            const std::string& outputFilename,
            std::mutex& usageMtx,
            const std::string& usageFilename);

        // REQUIRED BY INHERITED CLASS
        virtual bool handleConnection(
            CivetServer *server,
            const struct mg_connection *conn);

        virtual void handleReadyState(
            CivetServer *server,
            struct mg_connection *conn);

        virtual bool handleData(
            CivetServer *server,
            struct mg_connection *conn,
            int bits,
            char *data,
            size_t data_len);

        virtual void handleClose(
            CivetServer *server,
            const struct mg_connection *conn);

    private:
        std::default_random_engine &rng;
        
        PrsonaClient *prsonaClient;

        const std::vector<std::string> serverIPs;
        const std::vector<int> serverPorts;

        std::mutex& outputMtx;
        const std::string outputFilename;
        std::mutex& usageMtx;
        const std::string usageFilename;

        // RESPONSE ROUTER FUNCTION
        void generate_response(
            CivetServer *server,
            struct mg_connection *conn,
            const char *filename);

        // REPUTATION PROOF RESPONSE
        void verify_reputation_proof(
            CivetServer *civetServer,
            struct mg_connection *conn,
            const char *filename);
};

#endif
