#ifndef GROUP_H_INCLUDED
#define GROUP_H_INCLUDED

#include <NTL/ZZ.h>
#include <crypto/ostring.h>
#include <crypto/crypto.h>


template<class group> 
	typename group::GrpElt generic_mul(const NTL::ZZ& m, 
		const typename group::GrpElt& a, const group& grp)
{
	int i, k;
	typename group::GrpElt acc;

	if (m < 0) throw General_error();

	acc = grp.ident();
	k = NumBits(m);
	for (i = k-1; i >= 0; i--)
	{
		acc = grp.add(acc, acc);
		if (bit(m, i)) acc = grp.add(acc, a);
	}

	return acc;
}


template<class group>
	int generic_subgroup(const typename group::GrpElt& a, const group& grp)
	{
		if (grp.nu == 1)
			return 1;
		else
			return grp.is_ident(grp.mul(grp.mu, a));
	}


inline int consistent(unsigned int f1, unsigned int f2, unsigned int f3)
{
	unsigned int v = f1 | f2 | f3;
	return ((v - 1U) & v) == 0;
}

			
		 

class modp_group
{
 public:
	typedef NTL::ZZ GrpElt;

	NTL::ZZ p;
        NTL::ZZ mu, nu; 
	NTL::ZZ g;

	void writetofile(std::ostream& ofile) const;
	modp_group(int lmu=160, int lp=512);
	modp_group(std::istream& ifile);

	void PrettyPrint(std::ostream&);	

	ostring encode(const NTL::ZZ& a) const;
	ostring pencode(const NTL::ZZ& a) const;

	int decode1(NTL::ZZ& x, const ostring& s, int pos, unsigned int& fmt) const;
	int decode(NTL::ZZ& x, const ostring& s, int pos) const
		{ unsigned int fmt; return decode1(x, s, pos, fmt); }

	NTL::ZZ ident() const;
	int is_ident(const NTL::ZZ& a) const;
	int equal(const NTL::ZZ& a, const NTL::ZZ& b) const;
	NTL::ZZ add(const NTL::ZZ& a, const NTL::ZZ& b) const;
	NTL::ZZ mul(const NTL::ZZ& m, const NTL::ZZ& a) const
		{ return generic_mul(m, a, *this); }

	int subgroup(const NTL::ZZ& a) const
		{ return generic_subgroup(a, *this); }

	void writetofile(std::ostream& ofile, const NTL::ZZ& a) const
		{ ofile << a << "\n"; }

	void readfromfile(std::istream& ifile, NTL::ZZ& a) const
		{ ifile >> a; } 

	void print_fmt(std::ostream& tofile) { };
	
};


class ecmodp_point
{
 public:
	int inf;
	NTL::ZZ x, y;
};

enum fmt_t { uncompressed_fmt, compressed_fmt, hybrid_fmt };


class ecmodp_group
{
 public:
	typedef ecmodp_point GrpElt;

	NTL::ZZ p, a, b;
        NTL::ZZ mu, nu; 
	ecmodp_point g;
	fmt_t encoding_fmt;

	void writetofile(std::ostream& ofile) const;
	ecmodp_group(std::istream& ifile);

	void PrettyPrint(std::ostream&);

	ostring encode(const ecmodp_point& a) const;
	ostring pencode(const ecmodp_point& a) const;

	int decode1(ecmodp_point& x, const ostring& s, int pos, 
                    unsigned int& fmt) const;
	int decode(ecmodp_point& x, const ostring& s, int pos) const
		{ unsigned int fmt; return decode1(x, s, pos, fmt); }

	ecmodp_point ident() const;
	int is_ident(const ecmodp_point& a) const;
	int equal(const ecmodp_point& a, const ecmodp_point& b) const;
	ecmodp_point add(const ecmodp_point& a, const ecmodp_point& b) const;
	ecmodp_point mul(const NTL::ZZ& m, const ecmodp_point& a) const
		{ return generic_mul(m, a, *this); }

	int subgroup(const ecmodp_point& a) const
		{ return generic_subgroup(a, *this); }

	void writetofile(std::ostream& ofile, const ecmodp_point& a) const
		{ ofile << a.inf << "\n" << a.x << "\n" << a.y << "\n"; }

	void readfromfile(std::istream& ifile, ecmodp_point& a) const
		{ ifile >> a.inf >> a.x >> a.y; }

	void print_fmt(std::ostream& tofile)
		{
			tofile << "\n\nEncoding format = ";
			if (encoding_fmt == 0)
				tofile << "uncompressed_fmt";
			else if (encoding_fmt == 1)
				tofile << "compressed_fmt";
			else
				tofile << "hybrid_fmt";
		}
	
};


class ecgf2_point {
public:
	int inf;
	NTL::GF2X x, y;
};

class ecgf2_group
{
 public:
	typedef ecgf2_point GrpElt;

	NTL::GF2X p, a, b;
        NTL::ZZ mu, nu; 
	ecgf2_point g;
	fmt_t encoding_fmt;

	void writetofile(std::ostream& ofile) const;
	ecgf2_group(std::istream& ifile);

	void PrettyPrint(std::ostream&);

	ostring encode(const ecgf2_point& a) const;
	ostring pencode(const ecgf2_point& a) const;

	int decode1(ecgf2_point& x, const ostring& s, int pos, 
                    unsigned int& fmt) const;
	int decode(ecgf2_point& x, const ostring& s, int pos) const
		{ unsigned int fmt; return decode1(x, s, pos, fmt); }

	ecgf2_point ident() const;
	int is_ident(const ecgf2_point& a) const;
	int equal(const ecgf2_point& a, const ecgf2_point& b) const;
	ecgf2_point add(const ecgf2_point& a, const ecgf2_point& b) const;
	ecgf2_point mul(const NTL::ZZ& m, const ecgf2_point& a) const
		{ return generic_mul(m, a, *this); }

	int subgroup(const ecgf2_point& a) const
		{ return generic_subgroup(a, *this); }

	void writetofile(std::ostream& ofile, const ecgf2_point& a) const
		{ ofile << a.inf << "\n" << a.x << "\n" << a.y << "\n"; }

	void readfromfile(std::istream& ifile, ecgf2_point& a) const
		{ ifile >> a.inf >> a.x >> a.y; }

	void print_fmt(std::ostream& tofile)
                {
                        tofile << "\n\nEncoding format = ";
                        if (encoding_fmt == 0)
                                tofile << "uncompressed_fmt";
                        else if (encoding_fmt == 1)
                                tofile << "compressed_fmt";
                        else
                                tofile << "hybrid_fmt";
                }
	
};



void prettyprint(const char*, const NTL::ZZ&, std::ostream& tofile);
void prettyprint(const char*, const ecmodp_point&, std::ostream& tofile);
void prettyprint(const char*, const ecgf2_point&, std::ostream& tofile);

#endif  /* GROUP_H_INCLUDED */
