import 'mocha';
import { expect } from 'chai';
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgumentCollection';
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';

describe('FunctionCallArgumentCollection', () => {
    describe('addArgument', () => {
        it('throws if argument is undefined', () => {
            // arrange
            const errorMessage = 'undefined argument';
            const arg = undefined;
            const sut = new FunctionCallArgumentCollection();
            // act
            const act = () => sut.addArgument(arg);
            // assert
            expect(act).to.throw(errorMessage);
        });
        it('throws if parameter value is already provided', () => {
            // arrange
            const duplicateParameterName = 'duplicateParam';
            const errorMessage = `argument value for parameter ${duplicateParameterName} is already provided`;
            const arg1 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
            const arg2 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
            const sut = new FunctionCallArgumentCollection();
            // act
            sut.addArgument(arg1);
            const act = () => sut.addArgument(arg2);
            // assert
            expect(act).to.throw(errorMessage);
        });
    });
    describe('getAllParameterNames', () => {
        it('returns as expected', () => {
            // arrange
            const testCases = [ {
                name: 'no args',
                args: [],
                expected: [],
            }, {
                name: 'with some args',
                args: [
                    new FunctionCallArgumentStub().withParameterName('a-param-name'),
                    new FunctionCallArgumentStub().withParameterName('b-param-name')],
                expected: [ 'a-param-name', 'b-param-name'],
            }];
            for (const testCase of testCases) {
                it(testCase.name, () => {
                    const sut = new FunctionCallArgumentCollection();
                    // act
                    for (const arg of testCase.args) {
                        sut.addArgument(arg);
                    }
                    const actual = sut.getAllParameterNames();
                    // assert
                    expect(actual).to.equal(testCase.expected);
                });
            }
        });
    });
    describe('getArgument', () => {
        it('throws if parameter name is undefined', () => {
            // arrange
            const expectedError = 'undefined parameter name';
            const undefinedValues = [ '', undefined ];
            for (const undefinedValue of undefinedValues) {
                const sut = new FunctionCallArgumentCollection();
                // act
                const act = () => sut.getArgument(undefinedValue);
                // assert
                expect(act).to.throw(expectedError);
            }
        });
        it('throws if argument does not exist', () => {
            // arrange
            const parameterName = 'nonExistingParam';
            const expectedError = `parameter does not exist: ${parameterName}`;
            const sut = new FunctionCallArgumentCollection();
            // act
            const act = () => sut.getArgument(parameterName);
            // assert
            expect(act).to.throw(expectedError);
        });
        it('returns argument as expected', () => {
            // arrange
            const expected = new FunctionCallArgumentStub()
                .withParameterName('expectedName')
                .withArgumentValue('expectedValue');
            const sut = new FunctionCallArgumentCollection();
            // act
            sut.addArgument(expected);
            const actual = sut.getArgument(expected.parameterName);
            // assert
            expect(actual).to.equal(expected);
        });
    });
    describe('hasArgument', () => {
        it('throws if parameter name is undefined', () => {
            // arrange
            const expectedError = 'undefined parameter name';
            const undefinedValues = [ '', undefined ];
            for (const undefinedValue of undefinedValues) {
                const sut = new FunctionCallArgumentCollection();
                // act
                const act = () => sut.hasArgument(undefinedValue);
                // assert
                expect(act).to.throw(expectedError);
            }
        });
        describe('returns as expected', () => {
            // arrange
            const testCases = [ {
                name: 'argument exists',
                parameter: 'existing-parameter-name',
                args: [
                    new FunctionCallArgumentStub().withParameterName('existing-parameter-name'),
                    new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name'),
                ],
                expected: true,
            },
            {
                name: 'argument does not exist',
                parameter: 'not-existing-parameter-name',
                args: [
                    new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-b'),
                    new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-a'),
                ],
                expected: false,
            }];
            for (const testCase of testCases) {
                it(`"${testCase.name}" returns "${testCase.expected}"`, () => {
                    const sut = new FunctionCallArgumentCollection();
                    // act
                    for (const arg of testCase.args) {
                        sut.addArgument(arg);
                    }
                    const actual = sut.hasArgument(testCase.parameter);
                    // assert
                    expect(actual).to.equal(testCase.expected);
                });
            }
        });
    });
});
