/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.ov.mixing.commons.proofs.bg.commitments;

import java.math.BigInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.ov.mixing.commons.tools.ExponentTools;
import com.scytl.ov.mixing.commons.tools.MultiExponentiation;

/**
 * Encapsulates a private commitment.
 */
public class PrivateCommitment {

    private static final Logger LOG = LoggerFactory.getLogger("std");
    
    private final Exponent r;

    private final Exponent[] exponents;

    private final CommitmentParams params;

    private final MultiExponentiation multiExponentiation;

    private ZpGroupElement commitment;

    public PrivateCommitment(final Exponent[] m, final Exponent r, final CommitmentParams params,
            final MultiExponentiation multiExponentiation) {

        this.params = params;
        this.exponents = m.clone();
        this.r = r;
        this.multiExponentiation = multiExponentiation;
    }

    public PrivateCommitment(final Exponent[] exponents, final BigInteger order, final CommitmentParams params,
            final MultiExponentiation multiExponentiation) {
        this(exponents, ExponentTools.getRandomExponent(order), params, multiExponentiation);
    }

    public PrivateCommitment(final Exponent exponent, final Exponent exponentR, final CommitmentParams params,
            final MultiExponentiation multiExponentiation) {
        this(new Exponent[] {exponent }, exponentR, params, multiExponentiation);
    }

    public PrivateCommitment(final Exponent exponent, final CommitmentParams params,
            final MultiExponentiation multiExponentiation) {
        this(new Exponent[] {exponent }, exponent.getQ(), params, multiExponentiation);
    }

    public void commit() {

        try {
            if (exponents.length > params.getG().length) {
                LOG.warn("To much exponents to be commited. " + exponents.length);
            } else {
                final ZpGroupElement aux = params.getH().exponentiate(r);

                final ZpSubgroup zpGroup = params.getGroup();
                ZpGroupElement[] bases = new ZpGroupElement[exponents.length];
                System.arraycopy(params.getG(), 0, bases, 0, exponents.length);

                final ZpGroupElement element = multiExponentiation.computeMultiExpo(zpGroup, bases, exponents);

                commitment = aux.multiply(element);
            }

        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }

    public Exponent[] getM() {
        return exponents;
    }

    public Exponent getR() {
        return r;
    }

    public PrivateCommitment multiply(final PrivateCommitment com) {

        try {
            if (exponents.length != com.getM().length) {
                LOG.warn("Trying to multiply things that can not be multiplied");
                return null;
            } else {
                final Exponent[] mresult = new Exponent[exponents.length];
                for (int i = 0, length = exponents.length; i < length; i++) {
                    mresult[i] = exponents[i].add(com.getM()[i]);
                }

                final Exponent rresult = r.add(com.getR());

                final PrivateCommitment result =
                    new PrivateCommitment(mresult, rresult, params, multiExponentiation);

                return result;
            }

        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }

    public PrivateCommitment exponentiate(final Exponent e) {

        try {

            final Exponent[] mresult = new Exponent[exponents.length];

            for (int i = 0, length = exponents.length; i < length; i++) {
                mresult[i] = exponents[i].multiply(e);
            }

            final Exponent rresult = r.multiply(e);

            final PrivateCommitment result =
                new PrivateCommitment(mresult, rresult, params, multiExponentiation);

            return result;

        } catch (GeneralCryptoLibException ex) {
            throw new CryptoLibException(ex);
        }
    }

    public PublicCommitment makePublicCommitment() {
        if (commitment == null) {
            commit();
        }
        return new PublicCommitment(commitment);
    }
}
