#ifndef __PRSONA_SERVER_HPP
#define __PRSONA_SERVER_HPP

#include <vector>
#include <mutex>

#include "BGN.hpp"
#include "Curvepoint.hpp"
#include "Bipoint.hpp"

#include "base.hpp"
#include "EGCiphertext.hpp"
#include "proof.hpp"

class PrsonaServer : public PrsonaBase {
    public:
        // CONSTRUCTORS
        PrsonaServer(
            size_t numServers);
        PrsonaServer(
            size_t numServers,
            const BGN& other_bgn);

        PrsonaServer(
            const PrsonaServer& other);
        PrsonaServer(
            PrsonaServer&& other);
        PrsonaServer &operator=(
            const PrsonaServer& other);
        PrsonaServer &operator=(
            PrsonaServer&& other);
        ~PrsonaServer();

        // BASIC PUBLIC SYSTEM INFO GETTERS
        BGNPublicKey get_bgn_public_key() const;
        size_t get_num_clients() const;
        size_t get_num_servers() const;

        Twistpoint get_fresh_generator(
            std::vector<Proof>& pi
        ) const;
        
        // FRESH GENERATOR CALCULATION
        Twistpoint add_curr_seed_to_generator(
            std::vector<Proof>& pi,
            const Twistpoint& currGenerator
        ) const;
        Twistpoint add_next_seed_to_generator(
            std::vector<Proof>& pi,
            const Twistpoint& currGenerator
        ) const;
        Twistpoint add_rand_seed_to_generator(
            std::vector<Proof>& pi,
            const Twistpoint& currGenerator
        ) const;

        // ENCRYPTED DATA GETTERS
        std::vector<TwistBipoint> get_current_votes_by(
            Proof& pi,
            const Twistpoint& shortTermPublicKey
        ) const;
        std::vector<std::vector<TwistBipoint>> get_all_current_votes(
            Proof& pi
        ) const;
        EGCiphertext get_current_user_encrypted_tally(
            Proof& pi,
            const Twistpoint& shortTermPublicKey
        ) const;
        CurveBipoint get_current_server_encrypted_tally(
            Proof& pi,
            const Twistpoint& shortTermPublicKey
        ) const;
        std::vector<Twistpoint> get_current_pseudonyms(
            Proof& pi
        ) const;
        std::vector<Twistpoint> get_current_pseudonyms() const;

        // PROOF COMMITMENT GETTERS
        Proof get_vote_row_commitment(
            const Twistpoint& request
        ) const;
        Proof get_vote_matrix_commitment() const;
        Proof get_user_tally_commitment(
            const Twistpoint& request
        ) const;
        Proof get_server_tally_commitment(
            const Twistpoint& request
        ) const;
        Proof get_pseudonyms_commitment() const;
        void print_current_commitments() const;

        // CLIENT INTERACTIONS
        void add_new_client(
            std::vector<Proof>& proofOfValidAddition,
            const Proof& proofOfValidKey,
            const Twistpoint& shortTermPublicKey);
        bool receive_vote(
            const std::vector<Proof>& pi,
            const std::vector<TwistBipoint>& newVotes,
            const Twistpoint& shortTermPublicKey);

        void print_scores(
            const std::vector<CurveBipoint>& scores);

        // CONSTRUCTOR HELPERS
        const BGN& get_bgn_details() const;

        bool initialize_fresh_generator(
            const std::vector<Proof>& pi,
            const Twistpoint& firstGenerator);

        bool set_EG_blind_generator(
            const std::vector<Proof>& pi,
            const Twistpoint& currGenerator);

        // EPOCH ROUNDS
        void build_up_midway_pseudonyms(
            std::vector<std::vector<std::vector<Proof>>>& pi,
            std::vector<std::vector<std::vector<Twistpoint>>>& permutationCommits,
            std::vector<std::vector<std::vector<Twistpoint>>>& freshPseudonymCommits,
            std::vector<std::vector<std::vector<Twistpoint>>>& freshPseudonymSeedCommits,
            std::vector<std::vector<std::vector<CurveBipoint>>>& serverTallyCommits,
            std::vector<std::vector<std::vector<std::vector<TwistBipoint>>>>& partwayVoteMatrixCommits,
            std::vector<std::vector<std::vector<std::vector<TwistBipoint>>>>& finalVoteMatrixCommits,
            Twistpoint& nextGenerator);

        void break_down_midway_pseudonyms(
            const std::vector<Proof>& generatorProof,
            std::vector<std::vector<std::vector<Proof>>>& pi,
            std::vector<std::vector<std::vector<Twistpoint>>>& permutationCommits,
            std::vector<std::vector<std::vector<Twistpoint>>>& freshPseudonymCommits,
            std::vector<std::vector<std::vector<Twistpoint>>>& freshPseudonymSeedCommits,
            std::vector<std::vector<std::vector<CurveBipoint>>>& serverTallyCommits,
            std::vector<std::vector<std::vector<std::vector<TwistBipoint>>>>& partwayVoteMatrixCommits,
            std::vector<std::vector<std::vector<std::vector<TwistBipoint>>>>& finalVoteMatrixCommits,
            std::vector<std::vector<std::vector<Twistpoint>>>& userTallyMaskCommits,
            std::vector<std::vector<std::vector<Twistpoint>>>& userTallyMessageCommits,
            std::vector<std::vector<std::vector<Twistpoint>>>& userTallySeedCommits,
            const Twistpoint& nextGenerator);

        bool accept_epoch_updates(
            const std::vector<std::vector<Proof>>& pi,
            const std::vector<std::vector<Twistpoint>>& permutationCommits,
            const std::vector<std::vector<Twistpoint>>& freshPseudonymCommits,
            const std::vector<std::vector<Twistpoint>>& freshPseudonymSeedCommits,
            const std::vector<std::vector<CurveBipoint>>& serverTallyCommits,
            const std::vector<std::vector<std::vector<TwistBipoint>>>& partwayVoteMatrixCommits,
            const std::vector<std::vector<std::vector<TwistBipoint>>>& finalVoteMatrixCommits,
            const std::vector<std::vector<Twistpoint>>& userTallyMaskCommits,
            const std::vector<std::vector<Twistpoint>>& userTallyMessageCommits,
            const std::vector<std::vector<Twistpoint>>& userTallySeedCommits,
            const Twistpoint& nextGenerator,
            bool doUserTallies);

        // DATA MAINTENANCE
        void export_new_user_update(
            std::vector<CurveBipoint>& otherPreviousVoteTallies,
            std::vector<Twistpoint>& otherCurrentPseudonyms,
            std::vector<EGCiphertext>& otherCurrentUserEncryptedTallies,
            std::vector<std::vector<TwistBipoint>>& otherVoteMatrix
        ) const;

        bool import_new_user_update(
            const std::vector<Proof>& pi,
            const std::vector<CurveBipoint>& otherPreviousVoteTallies,
            const std::vector<Twistpoint>& otherCurrentPseudonyms,
            const std::vector<EGCiphertext>& otherCurrentUserEncryptedTallies,
            const std::vector<std::vector<TwistBipoint>>& otherVoteMatrix);

        // SCORE TALLYING
        std::vector<Scalar> tally_scores();
        Scalar get_max_possible_score();
        void receive_tallied_scores(
            const std::vector<EGCiphertext>& userTallyScores,
            const std::vector<CurveBipoint>& serverTallyScores);
        void encrypt(
            CurveBipoint& element,
            const Scalar& value);

        // MULTI-THREADING
        friend void generate_permutation_commitment_r(
            const void *a,
            void *b,
            const void *c,
            void *d);

        friend void generate_pseudonym_commitment_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            void *e,
            void *f);

        friend void generate_server_tally_commitment_r(
            const void *a,
            void *b,
            const void *c,
            void *d);

        friend void generate_matrix_commitment_r(
            const void *a,
            void *b,
            void *c,
            void *d,
            const void *e,
            const void *f,
            void *g,
            void *h);

        friend void generate_user_tally_commitment_r(
            const void *a,
            const void *b,
            const void *c,
            const void *d,
            const void *e,
            void *f,
            void *g,
            void *h,
            void *i,
            void *j,
            void *k);

        friend void generate_permutation_proof_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e);

        friend void generate_pseudonym_proof_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h,
            const void *i,
            const void *j);

        friend void generate_server_tally_proof_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h,
            const void *i,
            const void *j);

        friend void generate_first_half_matrix_proof_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h);

        friend void generate_second_half_matrix_proof_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h);

        friend void generate_user_tally_proof_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h,
            const void *i,
            const void *j,
            const void *k,
            const void *l,
            const void *m,
            const void *n);

        friend void generate_tensor_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h,
            const void *i,
            const void *j);

        friend void verify_permutation_r(
            const void *a,
            void *b,
            const void *c,
            const void *d);

        friend void verify_pseudonym_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g);

        friend void verify_server_tally_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h);

        friend void verify_first_half_matrix_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g);

        friend void verify_second_half_matrix_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g);

        friend void verify_user_tally_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h,
            const void *i,
            const void *j);

        friend void verify_tensor_r(
            const void *a,
            void *b,
            const void *c,
            const void *d,
            const void *e,
            const void *f,
            const void *g,
            const void *h);

    private:
        // constants for servers
        size_t numServers;

        // Identical between all servers (but collaboratively constructed)
        std::mutex *decryptMtx;
        BGN bgnSystem;

        // Private; different for each server
        Scalar currentSeed;
        Scalar nextSeed;

        // The actual data, which is collaboratively updated by all servers
        std::vector<Proof> currentGeneratorProof;
        Twistpoint currentFreshGenerator;
        std::vector<CurveBipoint> previousVoteTallies;
        std::vector<Twistpoint> currentPseudonyms;
        std::vector<EGCiphertext> currentUserEncryptedTallies;
        std::vector<std::vector<TwistBipoint>> voteMatrix;

        void remove();
        void copy(const PrsonaServer& other);

        /**
         * NOTE: voteMatrix structure:
         * Each element represents a vote by <rowID> applied to <colID>.
         * The outer vector is a vector of rows and the inner vector is
         * a vector of encrypted votes.
         */

        // An imaginary class; it's just used right now to coordinate servers
        // in memory instead of via network action.
        friend class PrsonaServerEntity;

        // EPOCH HELPERS
        std::vector<std::vector<Proof>> epoch_calculations(
            std::vector<std::vector<Twistpoint>>& permutationCommits,
            std::vector<std::vector<Twistpoint>>& freshPseudonymCommits,
            std::vector<std::vector<Twistpoint>>& freshPseudonymSeedCommits,
            std::vector<std::vector<CurveBipoint>>& serverTallyCommits,
            std::vector<std::vector<std::vector<TwistBipoint>>>& partwayVoteMatrixCommits,
            std::vector<std::vector<std::vector<TwistBipoint>>>& finalVoteMatrixCommits,
            std::vector<std::vector<Twistpoint>>& userTallyMaskCommits,
            std::vector<std::vector<Twistpoint>>& userTallyMessageCommits,
            std::vector<std::vector<Twistpoint>>& userTallySeedCommits,
            const Scalar& power,
            const Twistpoint& nextGenerator,
            bool doUserTallies);

        std::vector<std::vector<Scalar>> generate_permutation_matrix(
            const Scalar& reorderSeed
        ) const;
        std::vector<std::vector<Twistpoint>> generate_commitment_matrix(
            const std::vector<std::vector<Scalar>>& permutations,
            std::vector<std::vector<Scalar>>& seeds
        ) const;

        std::vector<std::vector<Twistpoint>> generate_pseudonym_matrix(
            const std::vector<std::vector<Scalar>>& permutations,
            const Scalar& power,
            std::vector<std::vector<Scalar>>& seeds,
            std::vector<std::vector<Twistpoint>>& seedCommits
        ) const;

        std::vector<std::vector<CurveBipoint>> generate_server_tally_matrix(
            const std::vector<std::vector<Scalar>>& permutations,
            std::vector<std::vector<Scalar>>& seeds
        ) const;

        std::vector<std::vector<std::vector<TwistBipoint>>> generate_vote_tensor(
            const std::vector<std::vector<Scalar>>& permutations,
            const std::vector<std::vector<TwistBipoint>>& currVoteMatrix,
            std::vector<std::vector<std::vector<Scalar>>>& seeds,
            bool inverted
        ) const;

        std::vector<std::vector<TwistBipoint>> calculate_next_vote_matrix(
            const std::vector<std::vector<std::vector<TwistBipoint>>>& voteTensor
        ) const;

        void generate_vote_tensor_proofs(
            std::vector<std::vector<Proof>>& pi,
            const std::vector<std::vector<Scalar>>& permutations,
            const std::vector<std::vector<Scalar>>& permutationSeeds,
            const std::vector<std::vector<std::vector<Scalar>>>& matrixSeeds,
            const std::vector<std::vector<TwistBipoint>>& currMatrix,
            const std::vector<std::vector<Twistpoint>>& permutationCommits,
            const std::vector<std::vector<std::vector<TwistBipoint>>>& matrixCommits,
            bool inverted
        ) const;
        bool verify_vote_tensor_proofs(
            const std::vector<std::vector<Proof>>& pi,
            size_t start_offset,
            const std::vector<std::vector<TwistBipoint>>& currMatrix,
            const std::vector<std::vector<Twistpoint>>& permutationCommits,
            const std::vector<std::vector<std::vector<TwistBipoint>>>& matrixCommits,
            bool inverted
        ) const;

        void generate_user_tally_matrix(
            const std::vector<std::vector<Scalar>>& permutations,
            const Scalar& power,
            const Twistpoint& nextGenerator,
            const std::vector<Twistpoint>& currPseudonyms,
            std::vector<Twistpoint>& masks,
            std::vector<std::vector<Twistpoint>>& maskCommits,
            std::vector<Twistpoint>& messages,
            std::vector<std::vector<Twistpoint>>& messageCommits,
            std::vector<std::vector<Scalar>>& userTallySeeds,
            std::vector<std::vector<Twistpoint>>& userTallySeedCommits
        ) const;

        template <typename T>
        std::vector<std::vector<T>> generate_reordered_plus_power_matrix(
            const std::vector<std::vector<Scalar>>& permutations,
            const Scalar& power,
            const std::vector<T>& oldValues,
            std::vector<std::vector<Scalar>>& seeds,
            std::vector<std::vector<Twistpoint>>& seedCommits,
            const T& h
        ) const;

        template <typename T>
        std::vector<std::vector<T>> generate_reordered_matrix(
            const std::vector<std::vector<Scalar>>& permutations,
            const std::vector<T>& oldValues,
            std::vector<std::vector<Scalar>>& seeds,
            const T& h,
            bool cancelOut
        ) const;

        template <typename T>
        std::vector<std::vector<T>> transpose_matrix(
            const std::vector<std::vector<T>>& input
        ) const;

        std::vector<size_t> sort_data(
            const std::vector<Twistpoint>& inputs
        ) const;

        // A helper class for "ordering" data and for binary search
        struct SortingType {
            Twistpoint pseudonym;
            size_t index;

            bool operator<( const SortingType& rhs ) const
                { return pseudonym < rhs.pseudonym; }
        };

        template <typename T>
        T encrypt(
            const T& g,
            const T& h,
            const Scalar& plaintext,
            const Scalar& lambda
        ) const;

        bool update_data(
            const std::vector<std::vector<Twistpoint>>& freshPseudonymCommits,
            const std::vector<std::vector<CurveBipoint>>& serverTallyCommits,
            const std::vector<std::vector<std::vector<TwistBipoint>>>& voteMatrixCommits,
            const std::vector<std::vector<Twistpoint>>& userTallyMaskCommits,
            const std::vector<std::vector<Twistpoint>>& userTallyMessageCommits);

        bool pseudonyms_sorted(
            const std::vector<Twistpoint> newPseudonyms
        ) const;

        // DATA SAFEKEEPING
        std::vector<size_t> order_data();
        
        // BINARY SEARCH
        size_t binary_search(
            const Twistpoint& index
        ) const;

        // VALID VOTE PROOFS
        bool verify_vote_proof(
            const std::vector<Proof>& pi,
            const std::vector<TwistBipoint>& oldVotes,
            const std::vector<TwistBipoint>& newVotes,
            const Twistpoint& shortTermPublicKey
        ) const;
}; 

#endif