#!/usr/bin/env python3
#  Copyright 2016 Google Inc. All Rights Reserved.
#
# 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.
import pytest

from fruit_test_common import *

COMMON_DEFINITIONS = '''
    #include "test_common.h"

    struct X;

    struct Annotation1 {};
    using XAnnot = fruit::Annotated<Annotation1, X>;

    struct Annotation2 {};

    struct Annotation3 {};
    
    template <typename T>
    using WithNoAnnotation = T;
    
    template <typename T>
    using WithAnnotation1 = fruit::Annotated<Annotation1, T>;
    '''

def test_register_constructor_success_copyable_and_movable():
    source = '''
        struct X {
          INJECT(X()) = default;
          X(X&&) = default;
          X(const X&) = default;
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent();
        }

        int main() {
          fruit::Injector<X> injector(getComponent);
          injector.get<X*>();
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source)

def test_register_constructor_success_movable_only():
    source = '''
        struct X {
          INJECT(X()) = default;
          X(X&&) = default;
          X(const X&) = delete;
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent();
        }

        int main() {
          fruit::Injector<X> injector(getComponent);
          injector.get<X*>();
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source)

def test_register_constructor_success_not_movable():
    source = '''
        struct X {
          INJECT(X()) = default;
          X(X&&) = delete;
          X(const X&) = delete;
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent();
        }

        int main() {
          fruit::Injector<X> injector(getComponent);
          injector.get<X*>();
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source)

# TODO: consider moving to test_normalized_component.py
@pytest.mark.parametrize('XAnnot,YAnnot,MaybeConstYAnnot,ZAnnot', [
    ('X', 'Y', 'Y', 'Z'),
    ('X', 'Y', 'const Y', 'Z'),
    ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation2, const Y>', 'fruit::Annotated<Annotation3, Z>'),
])
def test_autoinject_with_annotation_success(XAnnot, YAnnot, MaybeConstYAnnot, ZAnnot):
    source = '''
        struct X {
          using Inject = X();
        };

        struct Y : public ConstructionTracker<Y> {
          using Inject = Y();
        };

        struct Z {
          using Inject = Z();
        };

        fruit::Component<ZAnnot, MaybeConstYAnnot, XAnnot> getComponent() {
          return fruit::createComponent();
        }
        
        fruit::Component<> getEmptyComponent() {
          return fruit::createComponent();
        }

        int main() {
          fruit::NormalizedComponent<> normalizedComponent(getEmptyComponent);
          fruit::Injector<MaybeConstYAnnot> injector(normalizedComponent, getComponent);

          Assert(Y::num_objects_constructed == 0);
          injector.get<YAnnot>();
          Assert(Y::num_objects_constructed == 1);
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source,
        locals())

def test_autoinject_annotation_in_signature_return_type():
    source = '''
        struct X {
          using Inject = XAnnot();
        };

        fruit::Component<XAnnot> getComponent() {
          return fruit::createComponent();
        }
        '''
    expect_compile_error(
        'InjectTypedefWithAnnotationError<X>',
        'C::Inject is a signature that returns an annotated type',
        COMMON_DEFINITIONS,
        source)

def test_autoinject_wrong_class_in_typedef():
    source = '''
        struct X {
          using Inject = X();
        };

        struct Y : public X {
        };

        fruit::Component<Y> getComponent() {
          return fruit::createComponent();
        }
        '''
    expect_compile_error(
        'InjectTypedefForWrongClassError<Y,X>',
        'C::Inject is a signature, but does not return a C. Maybe the class C has no Inject typedef and',
        COMMON_DEFINITIONS,
        source)

def test_register_constructor_error_abstract_class():
    source = '''
        struct X {
          X(int*) {}

          virtual void foo() = 0;
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent()
            .registerConstructor<fruit::Annotated<Annotation1, X>(int*)>();
        }
        '''
    if re.search('GNU|MSVC', CXX_COMPILER_NAME) is not None:
        expect_generic_compile_error(
            'invalid abstract return type'
            '|.X.: cannot instantiate abstract class',
            COMMON_DEFINITIONS,
            source)
    else:
        expect_compile_error(
            'CannotConstructAbstractClassError<X>',
            'The specified class can.t be constructed because it.s an abstract class',
            COMMON_DEFINITIONS,
            source)

def test_register_constructor_error_malformed_signature():
    source = '''
        struct X {
          X(int) {}
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent()
            .registerConstructor<X[]>();
        }
        '''
    expect_compile_error(
        'NotASignatureError<X\[\]>',
        'CandidateSignature was specified as parameter, but it.s not a signature. Signatures are of the form',
        COMMON_DEFINITIONS,
        source)

def test_register_constructor_error_malformed_signature_autoinject():
    source = '''
        struct X {
          using Inject = X[];
          X(int) {}
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent();
        }
        '''
    expect_compile_error(
        'InjectTypedefNotASignatureError<X,X\[\]>',
        'C::Inject should be a typedef to a signature',
        COMMON_DEFINITIONS,
        source)

@pytest.mark.parametrize('charPtrAnnot', [
    'char*',
    'fruit::Annotated<Annotation1, char*>',
])
def test_register_constructor_does_not_exist_error(charPtrAnnot):
    source = '''
        struct X {
          X(int*) {}
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent()
            .registerConstructor<X(charPtrAnnot)>();
        }
        '''
    expect_compile_error(
        'NoConstructorMatchingInjectSignatureError<X,X\(char\*\)>',
        'contains an Inject typedef but it.s not constructible with the specified types',
        COMMON_DEFINITIONS,
        source,
        locals())

@pytest.mark.parametrize('charPtrAnnot', [
    'char*',
    'fruit::Annotated<Annotation1, char*>',
])
def test_autoinject_constructor_does_not_exist_error(charPtrAnnot):
    source = '''
        struct X {
          using Inject = X(charPtrAnnot);
          X(int*) {}
        };

        fruit::Component<X> getComponent() {
          return fruit::createComponent();
        }
        '''
    expect_compile_error(
        'NoConstructorMatchingInjectSignatureError<X,X\(char\*\)>',
        'contains an Inject typedef but it.s not constructible with the specified types',
        COMMON_DEFINITIONS,
        source,
        locals())

def test_autoinject_abstract_class_error():
    source = '''
        struct X {
          using Inject = fruit::Annotated<Annotation1, X>();

          virtual void scale() = 0;
          // Note: here we "forgot" to implement scale() (on purpose, for this test) so X is an abstract class.
        };

        fruit::Component<fruit::Annotated<Annotation1, X>> getComponent() {
          return fruit::createComponent();
        }
        '''
    expect_compile_error(
        'CannotConstructAbstractClassError<X>',
        'The specified class can.t be constructed because it.s an abstract class.',
        COMMON_DEFINITIONS,
        source)

@pytest.mark.parametrize('WithAnnotation', [
    'WithNoAnnotation',
    'WithAnnotation1',
])
@pytest.mark.parametrize('YVariant', [
    'Y',
    'const Y',
    'Y*',
    'const Y*',
    'Y&',
    'const Y&',
    'std::shared_ptr<Y>',
    'fruit::Provider<Y>',
    'fruit::Provider<const Y>',
])
def test_register_constructor_with_param_success(WithAnnotation, YVariant):
    source = '''
        struct Y {};
        struct X {
          X(YVariant) {
          }
        };
        
        fruit::Component<WithAnnotation<Y>> getYComponent() {
          return fruit::createComponent()
            .registerConstructor<WithAnnotation<Y>()>();
        }

        fruit::Component<X> getComponent() {
          return fruit::createComponent()
            .install(getYComponent)
            .registerConstructor<X(WithAnnotation<YVariant>)>();
        }

        int main() {
          fruit::Injector<X> injector(getComponent);
          injector.get<X>();
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source,
        locals())

@pytest.mark.parametrize('WithAnnotation', [
    'WithNoAnnotation',
    'WithAnnotation1',
])
@pytest.mark.parametrize('YVariant', [
    'Y',
    'const Y',
    'const Y*',
    'const Y&',
    'fruit::Provider<const Y>',
])
def test_register_constructor_with_param_const_binding_success(WithAnnotation, YVariant):
    source = '''
        struct Y {};
        struct X {
          X(YVariant) {
          }
        };
        
        const Y y{};
        
        fruit::Component<WithAnnotation<const Y>> getYComponent() {
          return fruit::createComponent()
            .bindInstance<WithAnnotation<Y>, Y>(y);
        }

        fruit::Component<X> getComponent() {
          return fruit::createComponent()
            .install(getYComponent)
            .registerConstructor<X(WithAnnotation<YVariant>)>();
        }

        int main() {
          fruit::Injector<X> injector(getComponent);
          injector.get<X>();
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source,
        locals())

@pytest.mark.parametrize('WithAnnotation,YAnnotRegex', [
    ('WithNoAnnotation', 'Y'),
    ('WithAnnotation1', 'fruit::Annotated<Annotation1,Y>'),
])
@pytest.mark.parametrize('YVariant', [
    'Y*',
    'Y&',
    'std::shared_ptr<Y>',
    'fruit::Provider<Y>',
])
def test_register_constructor_with_param_error_nonconst_param_required(WithAnnotation, YAnnotRegex, YVariant):
    source = '''
        struct Y {};
        struct X {
          X(YVariant);
        };
        
        fruit::Component<WithAnnotation<const Y>> getYComponent();

        fruit::Component<> getComponent() {
          return fruit::createComponent()
            .install(getYComponent)
            .registerConstructor<X(WithAnnotation<YVariant>)>();
        }
        '''
    expect_compile_error(
        'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>',
        'The type T was provided as constant, however one of the constructors/providers/factories in this component',
        COMMON_DEFINITIONS,
        source,
        locals())

@pytest.mark.parametrize('WithAnnotation,YAnnotRegex', [
    ('WithNoAnnotation', 'Y'),
    ('WithAnnotation1', 'fruit::Annotated<Annotation1, Y>'),
])
@pytest.mark.parametrize('YVariant', [
    'Y*',
    'Y&',
    'std::shared_ptr<Y>',
    'fruit::Provider<Y>',
])
def test_register_constructor_with_param_error_nonconst_param_required_install_after(WithAnnotation, YAnnotRegex, YVariant):
    source = '''
        struct Y {};
        struct X {
          X(YVariant);
        };
        
        fruit::Component<WithAnnotation<const Y>> getYComponent();

        fruit::Component<> getComponent() {
          return fruit::createComponent()
            .registerConstructor<X(WithAnnotation<YVariant>)>()
            .install(getYComponent);
        }
        '''
    expect_compile_error(
        'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>',
        'The type T was provided as constant, however one of the constructors/providers/factories in this component',
        COMMON_DEFINITIONS,
        source,
        locals())

def test_register_constructor_requiring_nonconst_then_requiring_const_ok():
    source = '''
        struct X {};
        
        struct Y {
          Y(X&) {}
        };
        
        struct Z {
          Z(const X&) {}
        };

        fruit::Component<Y, Z> getRootComponent() {
          return fruit::createComponent()
            .registerConstructor<Y(X&)>()
            .registerConstructor<Z(const X&)>()
            .registerConstructor<X()>();
        }
        
        int main() {
          fruit::Injector<Y, Z> injector(getRootComponent);
          injector.get<Y>();
          injector.get<Z>();
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source,
        locals())

def test_register_constructor_requiring_nonconst_then_requiring_const_declaring_const_requirement_error():
    source = '''
        struct X {};
        
        struct Y {
          Y(X&) {}
        };
        
        struct Z {
          Z(const X&) {}
        };

        fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() {
          return fruit::createComponent()
            .registerConstructor<Y(X&)>()
            .registerConstructor<Z(const X&)>();
        }
        '''
    expect_compile_error(
        'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>',
        'The type T was declared as a const Required type in the returned Component, however',
        COMMON_DEFINITIONS,
        source,
        locals())

def test_register_constructor_requiring_const_then_requiring_nonconst_ok():
    source = '''
        struct X {};
        
        struct Y {
          Y(const X&) {}
        };
        
        struct Z {
          Z(X&) {}
        };

        fruit::Component<Y, Z> getRootComponent() {
          return fruit::createComponent()
            .registerConstructor<Y(const X&)>()
            .registerConstructor<Z(X&)>()
            .registerConstructor<X()>();
        }
        
        int main() {
          fruit::Injector<Y, Z> injector(getRootComponent);
          injector.get<Y>();
          injector.get<Z>();
        }
        '''
    expect_success(
        COMMON_DEFINITIONS,
        source,
        locals())

def test_register_constructor_requiring_const_then_requiring_nonconst_declaring_const_requirement_error():
    source = '''
        struct X {};
        
        struct Y {
          Y(const X&) {}
        };
        
        struct Z {
          Z(X&) {}
        };

        fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() {
          return fruit::createComponent()
            .registerConstructor<Y(const X&)>()
            .registerConstructor<Z(X&)>();
        }
        '''
    expect_compile_error(
        'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>',
        'The type T was declared as a const Required type in the returned Component, however',
        COMMON_DEFINITIONS,
        source,
        locals())

@pytest.mark.parametrize('YVariant,YVariantRegex', [
    ('Y**', r'Y\*\*'),
    ('std::shared_ptr<Y>*', r'std::shared_ptr<Y>\*'),
    ('std::nullptr_t', r'(std::)?nullptr(_t)?'),
    ('Y*&', r'Y\*&'),
    ('Y(*)()', r'Y(\((__cdecl)?\*\))?\((void)?\)'),
    ('fruit::Annotated<Annotation1, Y**>', r'Y\*\*'),
])
def test_register_constructor_with_param_error_type_not_injectable(YVariant, YVariantRegex):
    source = '''
        struct Y {};
        struct X {
          X(YVariant);
        };
        
        fruit::Component<> getComponent() {
          return fruit::createComponent()
            .registerConstructor<X(YVariant)>();
        }
        '''
    expect_compile_error(
        'NonInjectableTypeError<YVariantRegex>',
        'The type T is not injectable.',
        COMMON_DEFINITIONS,
        source,
        locals())


if __name__== '__main__':
    main(__file__)