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

import java.util.ArrayList;
import java.util.List;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.mathematical.groups.GroupElement;

/**
 * Provides functionality for compressing (using the multiplication operation) a
 * list of group elements.
 * 
 * @param <T>
 *            The type of elements that this compressor can handle.
 */
public final class GroupElementsCompressor<T extends GroupElement<?>> {

    /**
     * Compress a list of group elements.
     * <P>
     * 
     * @param elementsToBeCompressed
     *            The list of group elements to be compressed
     * @return The result of the compression process.
     * @throws GeneralCryptoLibException
     *             if {@code elementsToBeCompressed} is null or empty
     * @throws GeneralCryptoLibException
     *             if {@code elementsToBeCompressed} contains any elements that
     *             are not elements of the mathematical group on which this
     *             compressor operates.
     */
    @SuppressWarnings({"unchecked", "rawtypes" })
    public T compress(final List<T> elementsToBeCompressed)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmptyAndNoNulls(elementsToBeCompressed,
            "List of elements to be compressed");

        GroupElement runningTotal = elementsToBeCompressed.get(0);

        for (int i = 1; i < elementsToBeCompressed.size(); i++) {

            runningTotal =
                runningTotal.multiply(elementsToBeCompressed.get(i));
        }

        return (T) runningTotal;
    }

    /**
     * Builds a new list of elements from the input list, where the final
     * elements from the input list are compressed into a simple element.
     * <p>
     * Note: it is assumed that all of the elements in inputList are members of
     * the mathematical group that was passed to the constructor of this
     * GroupElementsCompressor.
     * 
     * @param outputListSize
     *            the number of elements that should be in the output list.
     * @param inputList
     *            the input list.
     * @return a new list of type {@code T}.
     * @throws GeneralCryptoLibException
     *             if {@code numElementsRequiredInNewList} &le; 1 or
     *             {@code inputList} is empty or elements of {@code inputList}
     *             are not belong to the compressor's group.
     */
    public List<T> buildListWithCompressedFinalElement(
            final int outputListSize, final List<T> inputList)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmptyAndNoNulls(inputList, "Element input list");
        validateOutputListLength(outputListSize, inputList);

        List<T> listWithCompressedFinalElement =
            buildFirstPartOfNewList(outputListSize, inputList);

        listWithCompressedFinalElement.add(compress(
            getListElementsToBeCompressed(outputListSize, inputList)));

        return listWithCompressedFinalElement;
    }

    private void validateOutputListLength(final int outputListSize,
            final List<T> inputList) throws GeneralCryptoLibException {

        Validate.isPositive(outputListSize,
            "Number of elements in output list");
        Validate.notGreaterThan(outputListSize, inputList.size(),
            "Number of elements in output list",
            "number of elements in input list");
    }

    private List<T> buildFirstPartOfNewList(
            final int numElementsRequiredInNewList,
            final List<T> inputList) {

        List<T> defensiveInputList = new ArrayList<T>();
        for (T element : inputList) {
            defensiveInputList.add(element);
        }

        List<T> listElementsWithCompressedElement = defensiveInputList
            .subList(0, numElementsRequiredInNewList - 1);

        return listElementsWithCompressedElement;
    }

    private List<T> getListElementsToBeCompressed(
            final int numElementsRequiredInNewList,
            final List<T> inputList) {

        List<T> defensiveInputList = new ArrayList<T>();
        for (T element : inputList) {
            defensiveInputList.add(element);
        }

        List<T> listElementsToBeCompressed = defensiveInputList.subList(
            numElementsRequiredInNewList - 1, defensiveInputList.size());

        return listElementsToBeCompressed;
    }
}
