/*
 * 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.cryptolib.proofs.proof;

import static com.scytl.cryptolib.commons.utils.validations.Validate.notNull;
import static com.scytl.cryptolib.commons.utils.validations.Validate.notNullOrEmptyAndNoNulls;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonValue;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.serialization.AbstractJsonSerializable;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;

/**
 * Class which represents a proof in a Zero-Knowledge Proof of Knowledge
 * (ZK-PoK) exchange using Maurer's unified PHI function.
 */
@JsonRootName("zkProof")
public final class Proof extends AbstractJsonSerializable {

    private final Exponent _hashValue;

    private final List<Exponent> _values;

    /**
     * Constructor for a Proof, which initializes the internal fields with the
     * received values.
     * 
     * @param hashValue
     *            a hash value calculated from a number of inputs.
     * @param proofValues
     *            a list of {@link Exponent}.
     * @throws GeneralCryptoLibException
     *             if arguments are invalid.
     */
    public Proof(Exponent hashValue, List<Exponent> proofValues)
            throws GeneralCryptoLibException {
        notNull(hashValue, "Hash value of proof");
        notNullOrEmptyAndNoNulls(proofValues, "List of proof exponents");
        for (Exponent exponent : proofValues) {
            Validate.equals(exponent.getQ(), hashValue.getQ(),
                "Zp subgroup q parameter of proof exponent value",
                "Zp subgroup q parameter of proof hash value");
        }
        _hashValue = hashValue;
        _values = new ArrayList<>(proofValues);
    }

    /**
     * Deserializes the instance from a string in JSON format.
     * 
     * @param json
     *            the JSON
     * @return the instance
     * @throws GeneralCryptoLibException
     *             failed to deserialize the instance.
     */
    public static Proof fromJson(String json)
            throws GeneralCryptoLibException {
        return fromJson(json, Proof.class);
    }

    /**
     * Creates an instance from a given memento during JSON deserialization.
     * 
     * @param memento
     *            the memento
     * @return
     * @throws GeneralCryptoLibException
     *             failed to create the instance.
     */
    @JsonCreator
    static Proof fromMemento(Memento memento)
            throws GeneralCryptoLibException {
        notNull(memento.values, "List of proof exponents");
        Exponent hashValue = new Exponent(memento.q, memento.hash);
        List<Exponent> proofValues =
            new ArrayList<>(memento.values.size());
        for (BigInteger value : memento.values) {
            proofValues.add(new Exponent(memento.q, value));
        }
        return new Proof(hashValue, proofValues);
    }

    /**
     * Retrieves the hash value of the proof.
     * 
     * @return the hash value.
     */
    public Exponent getHashValue() {

        return _hashValue;
    }

    /**
     * Retrieves the proof values .
     * 
     * @return the proof values.
     */
    public List<Exponent> getValuesList() {

        return new ArrayList<>(_values);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
            + ((_hashValue == null) ? 0 : _hashValue.hashCode());
        result =
            prime * result + ((_values == null) ? 0 : _values.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Proof other = (Proof) obj;
        if (_hashValue == null) {
            if (other._hashValue != null) {
                return false;
            }
        } else if (!_hashValue.equals(other._hashValue)) {
            return false;
        }
        if (_values == null) {
            if (other._values != null) {
                return false;
            }
        } else if (!_values.equals(other._values)) {
            return false;
        }
        return true;
    }

    /**
     * Returns a memento used during JSON serialization.
     * 
     * @return a memento.
     */
    @JsonValue
    Memento toMemento() {
        Memento memento = new Memento();
        memento.hash = _hashValue.getValue();
        memento.values = new ArrayList<>(_values.size());
        for (Exponent exponent : _values) {
            memento.values.add(exponent.getValue());
        }
        memento.q = _hashValue.getQ();
        return memento;
    }

    /**
     * Memento for JSON serialization.
     */
    static class Memento {
        @JsonProperty("hash")
        public BigInteger hash;

        @JsonProperty("values")
        public List<BigInteger> values;

        @JsonProperty("q")
        public BigInteger q;
    }
}
