/*
 * 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.math.BigInteger;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;

/**
 * Tests for {@link ZpSubgroup}.
 */
public class ZpSubgroupTest {

    private static BigInteger _p;

    private static BigInteger _q;

    private static BigInteger _g;

    private static ZpSubgroup _groupSmall;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        _p = new BigInteger("23");

        _q = new BigInteger("11");

        _g = new BigInteger("2");

        _groupSmall = new ZpSubgroup(_g, _p, _q);
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void givenAJsonStringThenCreateGroup()
            throws GeneralCryptoLibException {

        String jsonStr =
            "{\"zpSubgroup\":{\"q\":\"Cw==\",\"p\":\"Fw==\",\"g\":\"Ag==\"}}";

        ZpSubgroup newGroup = ZpSubgroup.fromJson(jsonStr);

        Assert.assertEquals(newGroup, _groupSmall);
    }

    @Test
    public void givenAGroupMemberThenCheckMembership()
            throws GeneralCryptoLibException {

        String errorMessage = "This element should be a group element";
        boolean expectedResult = true;

        // Create a group element member of the group
        BigInteger value = new BigInteger("1");
        ZpGroupElement groupMember =
            new ZpGroupElement(value, _groupSmall);

        createElementAndAssertMembership(groupMember, expectedResult,
            _groupSmall, errorMessage);
    }

    @Test
    public void givenAnElementWithDifferentPAndQThenCheckMembership()
            throws GeneralCryptoLibException {

        String errorMessage = "This element should be a group element";
        boolean expectedResult = false;

        BigInteger differentQ = new BigInteger("3");
        BigInteger differentP = new BigInteger("7");

        ZpSubgroup _group_g2_q3 =
            new ZpSubgroup(_g, differentP, differentQ);
        BigInteger value = new BigInteger("1");
        ZpGroupElement notGroupMember =
            new ZpGroupElement(value, _group_g2_q3);

        createElementAndAssertMembership(notGroupMember, expectedResult,
            _groupSmall, errorMessage);
    }

    @Test
    public void givenANonGroupMemberWithPositiveValueThenCheckMembership()
            throws GeneralCryptoLibException {

        String errorMessage = "This element should not be a group element";
        boolean expectedResult = false;

        // Create a group element that is not a member of the group
        BigInteger value = new BigInteger("5");
        ZpGroupElement groupMember =
            new ZpGroupElement(value, _groupSmall);

        createElementAndAssertMembership(groupMember, expectedResult,
            _groupSmall, errorMessage);
    }

    @Test
    public void givenANullElementThenCheckMembership() {

        assertEquals("This element should not be a group element", false,
            _groupSmall.isGroupMember(null));
    }

    @Test
    public void testGetIdentityElementOnce()
            throws GeneralCryptoLibException {

        ZpGroupElement identity =
            new ZpGroupElement(BigInteger.ONE, _groupSmall);

        assertEquals(
            "The element returned is not the expected identity element",
            true, identity.equals(_groupSmall.getIdentity()));
    }

    @Test
    public void testGetIdentityElementTwice()
            throws GeneralCryptoLibException {

        String errorMessage =
            "The %s element returned is not the expected identity element";

        ZpGroupElement identityElement =
            new ZpGroupElement(BigInteger.ONE, _groupSmall);

        ZpGroupElement firstIdentity = _groupSmall.getIdentity();
        ZpGroupElement secondIdentity = _groupSmall.getIdentity();

        assertEquals(String.format(errorMessage, "first"), true,
            identityElement.equals(firstIdentity));

        assertEquals(String.format(errorMessage, "second"), true,
            identityElement.equals(secondIdentity));
    }

    @Test
    public void testGetQ() {
        assertEquals("The Q element is not the expected one", _q,
            _groupSmall.getQ());
    }

    @Test
    public void testGetG() {
        assertEquals("The generator element is not the expected one", _g,
            _groupSmall.getGenerator().getValue());
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenANullGeneratorWhenCreatingGroupThenError()
            throws Exception {
        new ZpSubgroup(null, _p, _q);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenANullOrderWhenCreatingGroupThenError()
            throws GeneralCryptoLibException {
        new ZpSubgroup(_g, _p, null);
    }

    /**
     * Serializes a group to JSON format and shows that the group can be
     * successfully reconstructed after JSON deserialization.
     * 
     * @throws GeneralCryptoLibException
     */
    @Test
    public void testJsonSerializationAndDeserialization()
            throws GeneralCryptoLibException {

        String jsonStr = _groupSmall.toJson();

        ZpSubgroup reconstructedGroup = ZpSubgroup.fromJson(jsonStr);

        assertEquals(
            "The group could not be reconstructed from a JSON string.",
            reconstructedGroup, _groupSmall);
    }

    @Test
    public void testEqualsDifferentObjectType()
            throws GeneralCryptoLibException {

        String notAGroup = "I am not a group";
        String errorMessage = "Expected that objects would not be equals";
        assertTrue(errorMessage,
            !(new ZpSubgroup(_g, _p, _q).equals(notAGroup)));
    }

    @Test
    public void testEqualsTrue() throws GeneralCryptoLibException {

        String errorMessage = "Expected that objects would be equals";
        assertTrue(errorMessage,
            new ZpSubgroup(_g, _p, _q).equals(_groupSmall));
    }

    /**
     * Create an element with value {@code elementValue}, and assert the group
     * membership of the element is the expected value.
     *
     * @param groupElement
     *            The value of the group element to create.
     * @param isGroupElement
     *            The expected result of calling the isGroupMember method.
     * @param group
     *            The group that should be used to check the membership of the
     *            received element.
     * @param errorMessage
     *            Error message
     */
    private void createElementAndAssertMembership(
            final ZpGroupElement groupElement,
            final boolean isGroupElement, final ZpSubgroup group,
            final String errorMessage) {

        assertEquals(errorMessage, isGroupElement,
            group.isGroupMember(groupElement));
    }
}
