package com.alibaba.qlexpress4.runtime.instruction;

import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.exception.ErrorReporter;
import com.alibaba.qlexpress4.exception.MockErrorReporter;
import com.alibaba.qlexpress4.exception.QLRuntimeException;
import com.alibaba.qlexpress4.test.property.*;
import org.junit.Assert;
import org.junit.Test;

import java.math.BigDecimal;

import static org.junit.Assert.*;

/**
 * Author: TaoKan
 */
public class MethodInvokeInstructionTest {
    /**
     * child.getMethod1 (methodInstruction+callInstruction)
     * getMethod1
     */
    @Test
    public void equalTypeTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod1", 2, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child());
        parentParameters.push(1);
        parentParameters.push(2);
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        assertEquals(mockQContextParent.getValue().get(), 3);
    }
    
    @Test
    public void runnableTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction = new MethodInvokeInstruction(errorReporter, "run", 0, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push((Runnable)() -> {
        });
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
    }
    
    @Test
    public void defaultMethodTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "returnI", 0, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push((InterWithDefaultMethod)() -> 9);
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        assertEquals(9, mockQContextParent.getValue().get());
    }
    
    @Test
    public void childMethodMatchTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod11", 2, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child());
        parentParameters.push(1);
        parentParameters.push(2);
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), 3L);
    }
    
    @Test
    public void parentMethodMatch() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod12", 2, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child());
        parentParameters.push(1);
        parentParameters.push(2);
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), 3L);
    }
    
    @Test
    public void convertTypeAssignedMatch() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod5", 1, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child3());
        parentParameters.push(new Child3());
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), 0);
    }
    
    @Test
    public void arrayParamTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod6", 1, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child3());
        parentParameters.push(new Integer[] {5, 6});
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), 10);
    }
    
    @Test
    public void primitiveParamTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod7", 1, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child4());
        parentParameters.push(new Integer(5));
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), 5);
    }
    
    @Test
    public void primitiveImplicitTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod8", 1, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child5());
        parentParameters.push(5);
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), 5.0);
    }
    
    @Test
    public void bigIntegerImplicitTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod9", 1, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child6());
        parentParameters.push(5);
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), 5);
    }
    
    @Test
    public void doubleMatchTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "getMethod10", 1, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child6());
        parentParameters.push(5.0f);
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), new BigDecimal("5.0"));
    }
    
    @Test
    public void varArgTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "addField", 3, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child9());
        parentParameters.push(5);
        parentParameters.push("5.0");
        parentParameters.push("5.0");
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), "1");
    }
    
    @Test
    public void varArgTest2() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "addField1", 3, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child9());
        parentParameters.push(5);
        parentParameters.push("5.0");
        parentParameters.push("5.0");
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), "1");
    }
    
    @Test
    public void varArgTest3() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "addField2", 3, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child9());
        parentParameters.push(5);
        parentParameters.push("5.0");
        parentParameters.push("5.0");
        mockQContextParent.setParameters(parentParameters);
        methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        Assert.assertEquals(mockQContextParent.getValue().get(), "1");
    }
    
    @Test
    public void varArgNotMatchTest() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "addField3", 3, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child9());
        parentParameters.push(5);
        parentParameters.push("asd");
        parentParameters.push("sss");
        mockQContextParent.setParameters(parentParameters);
        try {
            methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
            Assert.fail();
        }
        catch (QLRuntimeException e) {
            Assert.assertEquals("METHOD_NOT_FOUND", e.getErrorCode());
        }
    }
    
    @Test
    public void varArgNotMatchTest2() {
        ErrorReporter errorReporter = new MockErrorReporter();
        MethodInvokeInstruction methodInvokeInstruction =
            new MethodInvokeInstruction(errorReporter, "addField", 3, false);
        MockQContextParent mockQContextParent = new MockQContextParent(true);
        ParentParameters parentParameters = new ParentParameters();
        parentParameters.push(new Child9());
        parentParameters.push(5);
        parentParameters.push(1);
        parentParameters.push(1);
        mockQContextParent.setParameters(parentParameters);
        try {
            methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS);
        }
        catch (QLRuntimeException e) {
            Assert.assertEquals("METHOD_NOT_FOUND", e.getErrorCode());
        }
    }
}
