/*
 * Copyright 2019 Web3 Labs Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.web3j.abi;

import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.*;

import org.junit.jupiter.api.Test;

import org.web3j.abi.datatypes.*;
import org.web3j.abi.datatypes.generated.Bytes10;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.abi.datatypes.generated.Uint32;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class DefaultFunctionEncoderTest {

    @Test
    public void testBuildMethodId() {
        assertEquals("0xcdcd77c0", DefaultFunctionEncoder.buildMethodId("baz(uint32,bool)"));
    }

    @Test
    public void testBuildMessageSignature() {
        assertEquals(
                "baz(uint32,bool)",
                DefaultFunctionEncoder.buildMethodSignature(
                        "baz", Arrays.asList(new Uint32(BigInteger.valueOf(69)), new Bool(true))));
    }

    @Test
    public void testBuildEmptyMethodSignature() {
        assertEquals(
                "empty()",
                DefaultFunctionEncoder.buildMethodSignature("empty", Collections.emptyList()));
    }

    @Test
    public void testEncodeConstructorEmpty() {
        assertEquals("", FunctionEncoder.encodeConstructor(Collections.emptyList()));
    }

    @Test
    public void testEncodeConstructorString() {
        assertEquals(
                "0000000000000000000000000000000000000000000000000000000000000020"
                        + "000000000000000000000000000000000000000000000000000000000000000a"
                        + "4772656574696e67732100000000000000000000000000000000000000000000",
                FunctionEncoder.encodeConstructor(
                        Collections.singletonList(new Utf8String("Greetings!"))));
    }

    @Test
    public void testEncodeConstructorUint() {
        assertEquals(
                "0000000000000000000000000000000000000000000000000000000000000001"
                        + "0000000000000000000000000000000000000000000000000000000000000020",
                FunctionEncoder.encodeConstructor(
                        Arrays.asList(
                                new Uint(BigInteger.ONE), new Uint(BigInteger.valueOf(0x20)))));
    }

    @Test
    public void testFunctionSimpleEncode() {
        Function function =
                new Function(
                        "baz",
                        Arrays.asList(new Uint32(BigInteger.valueOf(69)), new Bool(true)),
                        Collections.emptyList());

        assertEquals(
                "0xcdcd77c0"
                        + "0000000000000000000000000000000000000000000000000000000000000045"
                        + "0000000000000000000000000000000000000000000000000000000000000001",
                FunctionEncoder.encode(function));
    }

    @Test
    public void testFunctionMDynamicArrayEncode1() {
        Function function =
                new Function(
                        "sam",
                        Arrays.asList(
                                new DynamicBytes("dave".getBytes()),
                                new Bool(true),
                                new DynamicArray<>(
                                        new Uint(BigInteger.ONE),
                                        new Uint(BigInteger.valueOf(2)),
                                        new Uint(BigInteger.valueOf(3)))),
                        Collections.emptyList());

        assertEquals(
                "0xa5643bf2"
                        + "0000000000000000000000000000000000000000000000000000000000000060"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "00000000000000000000000000000000000000000000000000000000000000a0"
                        + "0000000000000000000000000000000000000000000000000000000000000004"
                        + "6461766500000000000000000000000000000000000000000000000000000000"
                        + "0000000000000000000000000000000000000000000000000000000000000003"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "0000000000000000000000000000000000000000000000000000000000000003",
                FunctionEncoder.encode(function));
    }

    @Test
    public void testFunctionMDynamicArrayEncode2() {
        Function function =
                new Function(
                        "f",
                        Arrays.asList(
                                new Uint(BigInteger.valueOf(0x123)),
                                new DynamicArray<>(
                                        new Uint32(BigInteger.valueOf(0x456)),
                                        new Uint32(BigInteger.valueOf(0x789))),
                                new Bytes10("1234567890".getBytes()),
                                new DynamicBytes("Hello, world!".getBytes())),
                        Collections.emptyList());

        assertEquals(
                "0x8be65246"
                        + "0000000000000000000000000000000000000000000000000000000000000123"
                        + "0000000000000000000000000000000000000000000000000000000000000080"
                        + "3132333435363738393000000000000000000000000000000000000000000000"
                        + "00000000000000000000000000000000000000000000000000000000000000e0"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "0000000000000000000000000000000000000000000000000000000000000456"
                        + "0000000000000000000000000000000000000000000000000000000000000789"
                        + "000000000000000000000000000000000000000000000000000000000000000d"
                        + "48656c6c6f2c20776f726c642100000000000000000000000000000000000000",
                FunctionEncoder.encode(function));
    }

    @Test
    public void testStaticStructEncode() {
        assertEquals(
                "0x3d761de6"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "000000000000000000000000000000000000000000000000000000000000000a",
                FunctionEncoder.encode(AbiV2TestFixture.setBarFunction));
    }

    @Test
    public void testDynamicStructEncode() {
        assertEquals(
                "0x2cf07395"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000080"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "6964000000000000000000000000000000000000000000000000000000000000"
                        + "0000000000000000000000000000000000000000000000000000000000000004"
                        + "6e616d6500000000000000000000000000000000000000000000000000000000",
                FunctionEncoder.encode(AbiV2TestFixture.setFooFunction));
    }

    @Test
    public void testDynamicStructWithStaticFieldEncode() {
        assertEquals(
                "0x9096c213"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "6964000000000000000000000000000000000000000000000000000000000000",
                FunctionEncoder.encode(AbiV2TestFixture.setBazFunction));
    }

    @Test
    public void testDynamicStructWithStaticFieldEncode2() {
        assertEquals(
                "0xbe9c5e34"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "6964000000000000000000000000000000000000000000000000000000000000",
                FunctionEncoder.encode(AbiV2TestFixture.setBozFunction));
    }

    @Test
    public void testStaticStructNestedEncode() {
        assertEquals(
                "0xad204a12"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "000000000000000000000000000000000000000000000000000000000000000a"
                        + "0000000000000000000000000000000000000000000000000000000000000001",
                FunctionEncoder.encode(AbiV2TestFixture.setFuzzFunction));
    }

    @Test
    public void testDynamicStructNestedEncode() {
        assertEquals(
                "0x8c9fb6f9"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000080"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "6964000000000000000000000000000000000000000000000000000000000000"
                        + "0000000000000000000000000000000000000000000000000000000000000004"
                        + "6e616d6500000000000000000000000000000000000000000000000000000000",
                FunctionEncoder.encode(AbiV2TestFixture.setNuuFunction));
    }

    @Test
    public void testDynamicStructNestedEncode2() {
        assertEquals(
                "0x6bb632a9"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000080"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "6964000000000000000000000000000000000000000000000000000000000000"
                        + "0000000000000000000000000000000000000000000000000000000000000004"
                        + "6e616d6500000000000000000000000000000000000000000000000000000000",
                FunctionEncoder.encode(AbiV2TestFixture.setNazFunction));
    }

    @Test
    public void testDynamicStructNestedEncode3() {
        assertEquals(
                "0xc036933d"
                        + "0000000000000000000000000000000000000000000000000000000000000020"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000100"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000080"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "6964000000000000000000000000000000000000000000000000000000000000"
                        + "0000000000000000000000000000000000000000000000000000000000000004"
                        + "6e616d6500000000000000000000000000000000000000000000000000000000"
                        + "0000000000000000000000000000000000000000000000000000000000000004"
                        + "6461746100000000000000000000000000000000000000000000000000000000",
                FunctionEncoder.encode(AbiV2TestFixture.setWizFunction));
    }

    @Test
    public void testABIv2ConstructorEncode() {
        class Struct1 extends DynamicStruct {
            public String id;
            public String name;

            public Struct1(String id, String name) {
                super(new Utf8String(id), new Utf8String(name));
                this.id = id;
                this.name = name;
            }
        }
        class Struct2 extends StaticStruct {
            public BigInteger id;
            public BigInteger name;

            public Struct2(BigInteger id, BigInteger name) {
                super(new Uint256(id), new Uint256(name));
                this.id = id;
                this.name = name;
            }
        }

        assertEquals(
                "0000000000000000000000000000000000000000000000000000000000000060"
                        + "0000000000000000000000000000000000000000000000000000000000000064"
                        + "0000000000000000000000000000000000000000000000000000000000000001"
                        + "0000000000000000000000000000000000000000000000000000000000000040"
                        + "0000000000000000000000000000000000000000000000000000000000000080"
                        + "0000000000000000000000000000000000000000000000000000000000000002"
                        + "6964000000000000000000000000000000000000000000000000000000000000"
                        + "0000000000000000000000000000000000000000000000000000000000000004"
                        + "6461746100000000000000000000000000000000000000000000000000000000",
                FunctionEncoder.encodeConstructor(
                        Arrays.asList(
                                new Struct1("id", "data"),
                                new Struct2(BigInteger.valueOf(100), BigInteger.ONE))));
    }

    @Test
    public void testMakeFunction()
            throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
                    InstantiationException, IllegalAccessException {

        Function expectedFunction =
                new Function(
                        "function",
                        Arrays.asList(new Bool(true)),
                        Arrays.asList(new TypeReference<Uint256>() {}));

        Function actualFunction =
                FunctionEncoder.makeFunction(
                        "function",
                        Arrays.asList("bool"),
                        Arrays.asList(true),
                        Arrays.asList("uint256"));

        assertEquals(actualFunction.getName(), expectedFunction.getName());

        Iterator<Type> expectedInput = expectedFunction.getInputParameters().iterator();
        for (Type actualInput : actualFunction.getInputParameters()) {
            assertEquals(actualInput.getValue(), expectedInput.next().getValue());
        }

        Iterator<TypeReference<Type>> expectedOutput =
                expectedFunction.getOutputParameters().iterator();
        for (TypeReference<Type> actualOutput : actualFunction.getOutputParameters()) {
            assertEquals(actualOutput.getType(), expectedOutput.next().getType());
        }
    }
}