/*
 This file is part of GNUnet.
 (C) 2014 Christian Grothoff (and other contributing authors)

 GNUnet is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published
 by the Free Software Foundation; either version 3, or (at your
 option) any later version.

 GNUnet is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with GNUnet; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA 02111-1307, USA.
 */

package org.gnunet.secretsharing;

import org.gnunet.construct.FixedSizeIntegerArray;
import org.gnunet.construct.Message;
import org.gnunet.util.BigIntegers;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;

public class Plaintext implements Message {
    @FixedSizeIntegerArray(signed = true, bitSize = 8, length = Parameters.elgamalBits / 8)
    public byte[] bits;

    public static Plaintext generate(BigInteger exp) {
        BigInteger val;
        val = Parameters.elgamalG.modPow(exp, Parameters.elgamalP);
        Plaintext plaintext = new Plaintext();
        plaintext.bits = BigIntegers.serializeUnsigned(val, Parameters.elgamalBits);
        return plaintext;
    }

    /**
     * Try all products of length l of the given generators.
     *
     * @param l number of factors
     * @param generators different factors to chose from
     * @return null if no solution was found, or an array of coefficients
     */
    public long[] bruteForceDiscreteLog(final long l, BigInteger[] generators) {
        BigInteger needle = new BigInteger(1, bits);
        if (l == 0) {
            if (needle.equals(BigInteger.ONE))
                return new long[0];
            return null;
        }
        long[] combo = new long[generators.length];
        boolean success = bruteForceDiscreteLog(l, combo, needle, BigInteger.ONE, generators, 0);
        if (success) {
            return combo;
        }
        return null;
    }

    private static boolean bruteForceDiscreteLog(final long l, long[] combo,
                                                final BigInteger needle, final BigInteger haystack,
                                                BigInteger[] generators, final int genIdx) {
        if (l == 0) {
            combo[genIdx] = 0;
            return haystack.equals(needle);
        }
        if (genIdx == generators.length - 1) {
            combo[genIdx] = l;
            BigInteger myHay = haystack.multiply(generators[genIdx].modPow(BigInteger.valueOf(l), Parameters.elgamalP)).mod(Parameters.elgamalP);
            return myHay.equals(needle);
        } else {
            BigInteger myHay = haystack;
            for (int i = 0; i <= l; i++) {
                combo[genIdx] = i;
                boolean success = bruteForceDiscreteLog(l - i, combo, needle, myHay, generators, genIdx + 1);
                if (success) {
                    return true;
                }
                myHay = myHay.multiply(generators[genIdx]).mod(Parameters.elgamalP);
            }
        }
        return false;
    }



    public Ciphertext encrypt(ThresholdPublicKey publicKey) {
        SecureRandom secureRandom = new SecureRandom();
        BigInteger c_1;
        BigInteger c_2;
        BigInteger y;
        BigInteger m;
        BigInteger h;

        m = new BigInteger(1, this.bits);
        h = new BigInteger(1, publicKey.bits);

        do {
            y = new BigInteger(Parameters.elgamalBits - 1, secureRandom);
        } while (y.compareTo(BigInteger.ONE) == 0 || y.compareTo(Parameters.elgamalQ) >= 0);

        c_1 = Parameters.elgamalG.modPow(y, Parameters.elgamalP);
        c_2 = m.multiply(h.modPow(y, Parameters.elgamalP)).mod(Parameters.elgamalP);

        Ciphertext ciphertext = new Ciphertext();
        ciphertext.c_1 = BigIntegers.serializeUnsigned(c_1, Parameters.elgamalBits);
        ciphertext.c_2 = BigIntegers.serializeUnsigned(c_2, Parameters.elgamalBits);

        return ciphertext;
    }

    @Override
    public String toString() {
        return "Plaintext{" +
                "bits=" + Arrays.toString(bits) +
                '}';
    }
}
