/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.crypt;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;

import freenet.node.FSParseException;
import freenet.support.Base64;
import freenet.support.HexUtil;
import freenet.support.IllegalBase64Exception;
import freenet.support.SimpleFieldSet;

/**
 * Holds DSA group parameters. These are the public (possibly shared) values
 * needed for the DSA algorithm
 */
public class DSAGroup extends CryptoKey {
	private static final long serialVersionUID = -1;
	
	protected static final int Q_BIT_LENGTH = 256;

    private final BigInteger p, q, g;

    public DSAGroup(BigInteger p, BigInteger q, BigInteger g) {
        this.p = p;
        this.q = q;
        this.g = g;
        if(p.signum() != 1 || q.signum() != 1 || g.signum() != 1)
        	throw new IllegalArgumentException();
    }

    private DSAGroup(DSAGroup group) {
        this.p = new BigInteger(1, group.p.toByteArray());
        this.q = new BigInteger(1, group.q.toByteArray());
        this.g = new BigInteger(1, group.g.toByteArray());
	}
    
    protected DSAGroup() {
        // For serialization.
        p = null;
        q = null;
        g = null;
    }

	/**
     * Parses a DSA Group from a string, where p, q, and g are in unsigned
     * hex-strings, separated by a commas
     */
    // see readFromField() below
    //public static DSAGroup parse(String grp) {
    //    StringTokenizer str=new StringTokenizer(grp, ",");
    //    BigInteger p,q,g;
    //    p = new BigInteger(str.nextToken(), 16);
    //    q = new BigInteger(str.nextToken(), 16);
    //    g = new BigInteger(str.nextToken(), 16);
    //    return new DSAGroup(p,q,g);
    //}
    public static CryptoKey read(InputStream i) throws IOException, CryptFormatException {
        BigInteger p, q, g;
        p = Util.readMPI(i);
        q = Util.readMPI(i);
        g = Util.readMPI(i);
        try {
        	DSAGroup group = new DSAGroup(p, q, g);
        	if(group.equals(Global.DSAgroupBigA)) return Global.DSAgroupBigA;
        	else return group;
        } catch (IllegalArgumentException e) {
        	throw (CryptFormatException)new CryptFormatException("Invalid group: "+e).initCause(e);
        }
    }

    @Override
	public String keyType() {
        return "DSA.g-" + p.bitLength();
    }

    public BigInteger getP() {
        return p;
    }

    public BigInteger getQ() {
        return q;
    }

    public BigInteger getG() {
        return g;
    }

    @Override
	public byte[] fingerprint() {
        BigInteger fp[] = new BigInteger[3];
        fp[0] = p;
        fp[1] = q;
        fp[2] = g;
        return fingerprint(fp);
    }

    @Override
	public byte[] asBytes() {
        byte[] pb = Util.MPIbytes(p);
        byte[] qb = Util.MPIbytes(q);
        byte[] gb = Util.MPIbytes(g);
        byte[] tb = new byte[pb.length + qb.length + gb.length];
        System.arraycopy(pb, 0, tb, 0, pb.length);
        System.arraycopy(qb, 0, tb, pb.length, qb.length);
        System.arraycopy(gb, 0, tb, pb.length + qb.length, gb.length);
        return tb;
    }

    @Override
	public boolean equals(Object o) {
        if (this == o) // Not necessary, but a very cheap optimization
                return true;
        return (o instanceof DSAGroup) && p.equals(((DSAGroup) o).p)
                && q.equals(((DSAGroup) o).q) && g.equals(((DSAGroup) o).g);
    }

    public boolean equals(DSAGroup o) {
        if (this == o) // Not necessary, but a very cheap optimization
                return true;
        return p.equals(o.p) && q.equals(o.q) && g.equals(o.g);
    }

    @Override
	public int hashCode() {
        return p.hashCode() ^ q.hashCode() ^ g.hashCode();
    }
    
	public SimpleFieldSet asFieldSet() {
		SimpleFieldSet fs = new SimpleFieldSet(true);
		fs.putSingle("p", Base64.encode(p.toByteArray()));
		fs.putSingle("q", Base64.encode(q.toByteArray()));
		fs.putSingle("g", Base64.encode(g.toByteArray()));
		return fs;
	}

	public static DSAGroup create(SimpleFieldSet fs) throws IllegalBase64Exception, FSParseException {
		String myP = fs.get("p");
		String myQ = fs.get("q");
		String myG = fs.get("g");
		if(myP == null || myQ == null || myG == null) throw new FSParseException("The given SFS doesn't contain required fields!");
		BigInteger p = new BigInteger(1, Base64.decode(myP));
		BigInteger q = new BigInteger(1, Base64.decode(myQ));
		BigInteger g = new BigInteger(1, Base64.decode(myG));
		DSAGroup dg = new DSAGroup(p, q, g);
		if(dg.equals(Global.DSAgroupBigA)) return Global.DSAgroupBigA;
		return dg;
	}
	
	@Override
	public String toString() {
		if(this == Global.DSAgroupBigA)
			return "Global.DSAgroupBigA";
		else return super.toString();
	}
	
	@Override
	public String toLongString() {
		if(this == Global.DSAgroupBigA)
			return "Global.DSAgroupBigA";
		return "p="+HexUtil.biToHex(p)+", q="+HexUtil.biToHex(q)+", g="+HexUtil.biToHex(g);
	}

	public DSAGroup cloneKey() {
		if(this == Global.DSAgroupBigA) return this;
		return new DSAGroup(this);
	}

}
