/*
 * ReplicatedPrep.h
 *
 */

#ifndef PROTOCOLS_REPLICATEDPREP_H_
#define PROTOCOLS_REPLICATEDPREP_H_

#include "Networking/Player.h"
#include "Processor/Data_Files.h"
#include "Processor/OnlineOptions.h"
#include "Processor/ThreadQueues.h"
#include "Protocols/ShuffleSacrifice.h"
#include "Protocols/MAC_Check_Base.h"
#include "Protocols/ShuffleSacrifice.h"
#include "edabit.h"

#include <array>

template<class T>
void bits_from_random(vector<T>& bits, typename T::Protocol& protocol);

template<class T>
class BufferPrep : public Preprocessing<T>
{
    template<class U, class V> friend class Machine;

    template<int>
    void buffer_inverses(true_type);
    template<int>
    void buffer_inverses(false_type) { throw runtime_error("no inverses"); }

protected:
    vector<array<T, 3>> triples;
    vector<array<T, 2>> squares;
    vector<array<T, 2>> inverses;
    vector<T> bits;
    vector<vector<InputTuple<T>>> inputs;

    vector<dabit<T>> dabits;

    int n_bit_rounds;

    SubProcessor<T>* proc;

    virtual void buffer_triples() { throw runtime_error("no triples"); }
    virtual void buffer_squares() { throw runtime_error("no squares"); }
    virtual void buffer_inverses();
    virtual void buffer_bits() { throw runtime_error("no bits"); }
    virtual void buffer_inputs(int player);

    // don't call this if T::Input requires input tuples
    void buffer_inputs_as_usual(int player, SubProcessor<T>* proc);

    virtual void buffer_dabits(ThreadQueues* = 0) { throw runtime_error("no daBits"); }
    virtual void buffer_edabits(int, ThreadQueues*) { throw runtime_error("no edaBits"); }
    virtual void buffer_sedabits(int, ThreadQueues*) { throw runtime_error("no sedaBits"); }

    virtual void buffer_edabits(bool strict, int n_bits,
            ThreadQueues* queues = 0);
    virtual void buffer_edabits_with_queues(bool strict, int n_bits);

    map<int, vector<dabit<T>>> personal_dabits;
    void get_personal_dabit(int player, T& a, typename T::bit_type& b);
    virtual void buffer_personal_dabits(int)
    { throw runtime_error("no personal daBits"); }

    void push_edabits(vector<edabitvec<T>>& edabits,
            const vector<T>& sums, const vector<vector<typename T::bit_type::part_type>>& bits,
            int buffer_size);
public:
    typedef T share_type;

    int buffer_size;

    static void basic_setup(Player& P) { (void) P; }
    static void setup(Player& P, typename T::mac_key_type alphai) { (void) P, (void) alphai; }
    static void teardown() {}

    static void edabit_sacrifice_buckets(vector<edabit<T>>&, size_t, bool, int,
            SubProcessor<T>&, int, int, const void* = 0)
    {
        throw runtime_error("sacrifice not available");
    }

    BufferPrep(DataPositions& usage);
    virtual ~BufferPrep();

    void clear();

    void get_three_no_count(Dtype dtype, T& a, T& b, T& c);
    void get_two_no_count(Dtype dtype, T& a, T& b);
    void get_one_no_count(Dtype dtype, T& a);
    void get_input_no_count(T& a, typename T::open_type& x, int i);
    void get_no_count(vector<T>& S, DataTag tag, const vector<int>& regs,
            int vector_size);

    virtual void get_dabit_no_count(T& a, typename T::bit_type& b);

    virtual T get_random();

    void push_triples(const vector<array<T, 3>>& triples)
    { this->triples.insert(this->triples.end(), triples.begin(), triples.end()); }
    void push_triple(const array<T, 3>& triple)
    { this->triples.push_back(triple); }

    void shrink_to_fit();

    void buffer_personal_triples(int, ThreadQueues*) {}
    void buffer_personal_triples(vector<array<T, 3>>&, int, int) {}

    SubProcessor<T>* get_proc() { return proc; }
    void set_proc(SubProcessor<T>* proc) { this->proc = proc; }
};

template<class T>
class BitPrep : public virtual BufferPrep<T>
{
protected:
    int base_player;

    typename T::Protocol* protocol;

    void buffer_ring_bits_without_check(vector<T>& bits, PRNG& G,
            int buffer_size);

public:
    BitPrep(SubProcessor<T>* proc, DataPositions& usage);
    ~BitPrep();

    void set_protocol(typename T::Protocol& protocol);

    void buffer_squares();

    void buffer_bits_without_check();
};

template<class T>
class RingPrep : public virtual BitPrep<T>
{
    typedef typename T::bit_type::part_type BT;

protected:
    void buffer_dabits_without_check(vector<dabit<T>>& dabits,
            int buffer_size = -1, ThreadQueues* queues = 0);
    template<int>
    void buffer_edabits_without_check(int n_bits, vector<T>& sums,
            vector<vector<typename T::bit_type::part_type>>& bits, int buffer_size,
            ThreadQueues* queues = 0);
    template<int>
    void buffer_edabits_without_check(int n_bits, vector<edabitvec<T>>& edabits,
            int buffer_size);

    void buffer_sedabits_from_edabits(int n_bits)
    { this->template buffer_sedabits_from_edabits<0>(n_bits, T::clear::characteristic_two); }
    template<int>
    void buffer_sedabits_from_edabits(int n_bits, false_type);
    template<int>
    void buffer_sedabits_from_edabits(int, true_type)
    { throw not_implemented(); }

    template<int>
    void sanitize(vector<edabitvec<T>>& edabits, int n_bits);

public:
    RingPrep(SubProcessor<T>* proc, DataPositions& usage);
    virtual ~RingPrep() {}

    vector<T>& get_bits() { return this->bits; }

    template<int>
    void sanitize(vector<edabit<T>>& edabits, int n_bits,
            int player = -1, ThreadQueues* queues = 0);
    template<int>
    void sanitize(vector<edabit<T>>& edabits, int n_bits, int player, int begin,
            int end);

    void buffer_dabits_without_check(vector<dabit<T>>& dabits,
            size_t begin, size_t end);
    template<int>
    void buffer_dabits_without_check(vector<dabit<T>>& dabits,
            size_t begin, size_t end,
            Preprocessing<typename T::bit_type::part_type>& bit_prep);

    template<int>
    void buffer_edabits_without_check(int n_bits, vector<T>& sums,
            vector<vector<typename T::bit_type::part_type>>& bits, int begin,
            int end);

    template<int>
    void buffer_personal_edabits_without_check(int n_bits, vector<T>& sums,
            vector<vector<BT> >& bits, SubProcessor<BT>& proc, int input_player,
            int begin, int end);
};

template<class T>
class SemiHonestRingPrep : public virtual RingPrep<T>
{
public:
    SemiHonestRingPrep(SubProcessor<T>* proc, DataPositions& usage) :
            BufferPrep<T>(usage), BitPrep<T>(proc, usage),
			RingPrep<T>(proc, usage)
    {
    }
    virtual ~SemiHonestRingPrep() {}

    virtual void buffer_bits() { this->buffer_bits_without_check(); }
    virtual void buffer_inputs(int player)
    { this->buffer_inputs_as_usual(player, this->proc); }

    virtual void buffer_dabits(ThreadQueues*)
    { this->buffer_dabits_without_check(this->dabits); }
    virtual void buffer_edabits(int n_bits, ThreadQueues*)
    { buffer_edabits<0>(n_bits, T::clear::characteristic_two); }
    template<int>
    void buffer_edabits(int n_bits, false_type)
    { this->template buffer_edabits_without_check<0>(n_bits,
            this->edabits[{false, n_bits}],
            OnlineOptions::singleton.batch_size); }
    template<int>
    void buffer_edabits(int, true_type)
    { throw not_implemented(); }
    virtual void buffer_sedabits(int n_bits, ThreadQueues*)
    { this->buffer_sedabits_from_edabits(n_bits); }
};

template<class T>
class MaliciousDabitOnlyPrep : public virtual RingPrep<T>
{
    template<int>
    void buffer_dabits(ThreadQueues* queues, true_type);
    template<int>
    void buffer_dabits(ThreadQueues* queues, false_type);

public:
    MaliciousDabitOnlyPrep(SubProcessor<T>* proc, DataPositions& usage) :
            BufferPrep<T>(usage), BitPrep<T>(proc, usage),
            RingPrep<T>(proc, usage)
    {
    }
    virtual ~MaliciousDabitOnlyPrep() {}

    virtual void buffer_dabits(ThreadQueues* queues);
};

template<class T>
class MaliciousRingPrep : public virtual MaliciousDabitOnlyPrep<T>
{
    typedef typename T::bit_type::part_type BT;

protected:
    void buffer_personal_edabits(int n_bits, vector<T>& sums,
            vector<vector<BT>>& bits, SubProcessor<BT>& proc, int input_player,
            bool strict, ThreadQueues* queues = 0);

    void buffer_edabits_from_personal(bool strict, int n_bits,
            ThreadQueues* queues);
    template<int>
    void buffer_edabits_from_personal(bool strict, int n_bits,
            ThreadQueues* queues, true_type);
    template<int>
    void buffer_edabits_from_personal(bool strict, int n_bits,
            ThreadQueues* queues, false_type);

    void buffer_personal_dabits(int input_player);
    void buffer_personal_dabits(int input_player, true_type);
    void buffer_personal_dabits(int input_player, false_type);

public:
    static void edabit_sacrifice_buckets(vector<edabit<T>>& to_check, size_t n_bits,
            bool strict, int player, SubProcessor<T>& proc, int begin, int end,
            const void* supply = 0)
    {
        EdabitShuffleSacrifice<T>().edabit_sacrifice_buckets(to_check, n_bits, strict,
                player, proc, begin, end, supply);
    }

    MaliciousRingPrep(SubProcessor<T>* proc, DataPositions& usage) :
            BufferPrep<T>(usage), BitPrep<T>(proc, usage),
            RingPrep<T>(proc, usage), MaliciousDabitOnlyPrep<T>(proc, usage)
    {
    }
    virtual ~MaliciousRingPrep() {}

    virtual void buffer_bits();
    virtual void buffer_edabits(bool strict, int n_bits, ThreadQueues* queues);
};

template<class T>
class ReplicatedRingPrep : public virtual BitPrep<T>
{
protected:
    void buffer_triples();
    void buffer_squares();

public:
    ReplicatedRingPrep(SubProcessor<T>* proc, DataPositions& usage) :
            BufferPrep<T>(usage), BitPrep<T>(proc, usage)
    {
    }

    virtual ~ReplicatedRingPrep() {}

    virtual void buffer_bits() { this->buffer_bits_without_check(); }
};

template<class T>
class ReplicatedPrep : public virtual ReplicatedRingPrep<T>,
        public virtual SemiHonestRingPrep<T>
{
public:
    ReplicatedPrep(SubProcessor<T>* proc, DataPositions& usage) :
            BufferPrep<T>(usage), BitPrep<T>(proc, usage),
            ReplicatedRingPrep<T>(proc, usage),
            RingPrep<T>(proc, usage),
            SemiHonestRingPrep<T>(proc, usage)
    {
    }

    ReplicatedPrep(DataPositions& usage, int = 0) :
            ReplicatedPrep(0, usage)
    {
    }

    void buffer_squares() { ReplicatedRingPrep<T>::buffer_squares(); }
    void buffer_bits();
};

#endif /* PROTOCOLS_REPLICATEDPREP_H_ */
