/*
 * 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.elgamal.bean;

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

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.mathematical.groups.impl.ZpGroupElement;

/**
 * Class which encapsulates the gamma and list of phi Zp group elements which
 * form a ciphertext.
 * <P>
 * This class is used to represent the result of ElGamal computations. These
 * computations include pre-computations and as well as encryptions. Instances
 * of this class can therefore act as both an input or as an output, depending
 * on the operation being performed.
 */
@JsonRootName("ciphertext")
public final class ElGamalComputationsValues
        extends BaseElGamalComputationsValues<ZpGroupElement> {
    /**
     * Creates a {@code ElGamalComputationsValues} using the specified gamma and
     * phi values.
     *
     * @param gammaElement
     *            the gamma (i.e. first) element of the ciphertext.
     * @param phiElements
     *            the phi elements of the ciphertext.
     * @throws GeneralCryptoLibException
     *             if the gamma element is null or the list of phi elements is
     *             null, empty or contains one or more null values.
     */
    public ElGamalComputationsValues(final ZpGroupElement gammaElement,
            final List<ZpGroupElement> phiElements)
            throws GeneralCryptoLibException {
        super(gammaElement, phiElements);
    }

    /**
     * Creates a {@code ElGamalComputationsValues} using the specified list of
     * elements. This list must fulfill this contract:
     * <ul>
     * <li>The first element has to be the {@code gamma}.</li>
     * <li>The other elements have to be the {@code phis} in the correct
     * order.</li>
     * <li>All elements should be members of the same mathematical group.</li>
     * </ul>
     *
     * @param ciphertext
     *            the sequence of the elements, first the {@code gamma}, then
     *            the {@code phi} values with the correct order.
     * @throws GeneralCryptoLibException
     *             if the ciphertext is null, empty or contains one or more null
     *             values.
     */
    public ElGamalComputationsValues(List<ZpGroupElement> ciphertext)
            throws GeneralCryptoLibException {
        super(ciphertext);
    }

    /**
     * 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 ElGamalComputationsValues fromJson(String json)
            throws GeneralCryptoLibException {
        return fromJson(json, ElGamalComputationsValues.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 ElGamalComputationsValues fromMemento(Memento memento)
            throws GeneralCryptoLibException {
        notNull(memento.gamma, "ElGamal gamma element");
        notNullOrEmpty(memento.phis, "List of ElGamal phi elements");
        notNull(memento.p, "Zp subgroup p parameter");
        notNull(memento.q, "Zp subgroup q parameter");
        ZpGroupElement gamma =
            new ZpGroupElement(memento.gamma, memento.p, memento.q);
        List<ZpGroupElement> phis = new ArrayList<>(memento.phis.size());
        for (BigInteger phi : memento.phis) {
            phis.add(new ZpGroupElement(phi, memento.p, memento.q));
        }
        return new ElGamalComputationsValues(gamma, phis);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result =
            prime * result + ((_gamma == null) ? 0 : _gamma.hashCode());
        result = prime * result + ((_phis == null) ? 0 : _phis.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;
        }
        ElGamalComputationsValues other = (ElGamalComputationsValues) obj;
        if (_gamma == null) {
            if (other._gamma != null) {
                return false;
            }
        } else if (!_gamma.equals(other._gamma)) {
            return false;
        }
        if (_phis == null) {
            if (other._phis != null) {
                return false;
            }
        } else if (!_phis.equals(other._phis)) {
            return false;
        }
        return true;
    }

    /**
     * Returns a memento used during JSON serialization.
     * 
     * @return a memento.
     */
    @JsonValue
    Memento toMemento() {
        Memento memento = new Memento();
        memento.gamma = _gamma.getValue();
        memento.phis = new ArrayList<>(_phis.size());
        for (ZpGroupElement element : _phis) {
            memento.phis.add(element.getValue());
        }
        memento.p = _gamma.getP();
        memento.q = _gamma.getQ();
        return memento;
    }

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

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

        @JsonProperty("p")
        public BigInteger p;

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