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

import java.math.BigInteger;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.serialization.AbstractJsonSerializable;
import com.scytl.cryptolib.commons.utils.validations.Validate;

/**
 * Implementation of an exponent that can be used for mathematical operations
 * defined by the Zp subgroup.
 * <P>
 * Instances of this class are immutable.
 */
@JsonRootName("exponent")
public final class Exponent extends AbstractJsonSerializable {

    private final BigInteger _q;

    private final BigInteger _value;

    /**
     * Creates an exponent with the specified Zp subgroup q parameter.
     * <P>
     * The value of the exponent should be within the range [0..q-1]. If the
     * value provided is not within this range, then the value assigned to the
     * exponent will be recalculated as follows:
     * <P>
     * {@code value = value mod q}
     * 
     * @param q
     *            the the Zp subgroup q parameter.
     * @param value
     *            the value of the exponent.
     * @throws GeneralCryptoLibException
     *             if the Zp subgroup q parameter is null or zero.
     */
    @JsonCreator
    public Exponent(@JsonProperty("q") BigInteger q,
            @JsonProperty("value") BigInteger value)
            throws GeneralCryptoLibException {
        validateInput(q, value);
        _q = q;
        _value = calculateValue(value);
    }

    /**
     * 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 Exponent fromJson(String json)
            throws GeneralCryptoLibException {
        return fromJson(json, Exponent.class);
    }

    /**
     * Retrieves the Zp subgroup q parameter for this exponent.
     * 
     * @return the Zp subgroup q parameter.
     */
    @JsonProperty("q")
    public BigInteger getQ() {
        return _q;
    }

    /**
     * Retrieves the value of the exponent.
     * 
     * @return the value of the exponent.
     */
    @JsonProperty("value")
    public BigInteger getValue() {
        return _value;
    }

    /**
     * Returns an {@code Exponent} whose value is
     * {@code (this + exponent) mod q}.
     * 
     * @param exponent
     *            the exponent to be added to this Exponent.
     * @return {@code (this + exponent) mod q}.
     * @throws GeneralCryptoLibException
     *             if exponents belong to different groups.
     */
    public Exponent add(Exponent exponent)
            throws GeneralCryptoLibException {
        confirmSameQ(exponent);
        BigInteger value = _value.add(exponent.getValue()).mod(_q);
        return new Exponent(_q, value);
    }

    /**
     * Returns an {@code Exponent} whose value is
     * {@code (this - exponent) mod q}.
     * 
     * @param exponent
     *            the exponent to be subtracted from this.
     * @return {@code (this - exponent) mod q}.
     * @throws GeneralCryptoLibException
     *             if exponents belong to different groups.
     */
    public Exponent subtract(Exponent exponent)
            throws GeneralCryptoLibException {
        confirmSameQ(exponent);
        BigInteger value = _value.subtract(exponent.getValue()).mod(_q);
        return new Exponent(_q, value);
    }

    /**
     * Returns an {@code Exponent} whose value is
     * {@code (this * exponent) mod q}.
     * 
     * @param exponent
     *            the exponent to be multiplied.
     * @return {@code (this * exponent) mod q}.
     * @throws GeneralCryptoLibException
     *             if exponents belong to different groups.
     */
    public Exponent multiply(Exponent exponent)
            throws GeneralCryptoLibException {
        confirmSameQ(exponent);
        BigInteger value = _value.multiply(exponent.getValue()).mod(_q);
        return new Exponent(_q, value);
    }

    /**
     * Returns an {@code Exponent} whose value is {@code (-this) mod q}.
     * 
     * @return {@code (-this mod q)}
     */
    public Exponent negate() {
        try {
            return new Exponent(_q, _value.negate().mod(_q));
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }

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

    @Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Exponent [_q=");
		builder.append(_q);
		builder.append(", _value=");
		builder.append(_value);
		builder.append("]");
		return builder.toString();
	}

	/**
     * Calculates the value to set for this {@code Exponent}. An exponent value
     * has to be a number between {@code 0} and {@code q-1} inclusive, so if the
     * received value is less than {@code 0} or greater than {@code q-1},
     * {@code mod q} has to be applied.
     *
     * @param exponentValue
     *            the value of the exponent.
     * @return the value to set to this exponent.
     */
    private BigInteger calculateValue(BigInteger exponentValue) {

        BigInteger result;
        if ((_q.compareTo(exponentValue) > 0) && (BigInteger.ZERO.compareTo(exponentValue) < 1)) {
            result = exponentValue;
        } else {
            result = exponentValue.mod(_q);
        }
        return result;
    }

    private void confirmSameQ(Exponent exponent)
            throws GeneralCryptoLibException {
        Validate.notNull(exponent, "Exponent");
        Validate.equals(exponent.getQ(), _q,
            "Zp subgroup q parameter of this exponent",
            "Zp subgroup q parameter of specified exponent");
    }

    private void validateInput(BigInteger q, BigInteger value)
            throws GeneralCryptoLibException {
        Validate.notNull(q, "Zp subgroup q parameter");
        Validate.notNull(value, "Exponent value");
        Validate.notLessThan(q, BigInteger.ONE, "Zp subgroup q parameter",
            "");
    }
}
