#!/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.

from absl.testing import parameterized
from fruit_test_common import *

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

    struct X;
    struct Y;
    struct Z;

    struct Annotation1 {};
    using XAnnot1 = fruit::Annotated<Annotation1, X>;
    using YAnnot1 = fruit::Annotated<Annotation1, Y>;
    using ZAnnot1 = fruit::Annotated<Annotation1, Z>;
    using ConstXAnnot1 = fruit::Annotated<Annotation1, const X>;
    using ConstYAnnot1 = fruit::Annotated<Annotation1, const Y>;
    using ConstZAnnot1 = fruit::Annotated<Annotation1, const Z>;

    struct Annotation2 {};
    using XAnnot2 = fruit::Annotated<Annotation2, X>;
    using YAnnot2 = fruit::Annotated<Annotation2, Y>;
    using ZAnnot2 = fruit::Annotated<Annotation2, Z>;
    using ConstXAnnot2 = fruit::Annotated<Annotation2, const X>;
    using ConstYAnnot2 = fruit::Annotated<Annotation2, const Y>;
    using ConstZAnnot2 = fruit::Annotated<Annotation2, const Z>;

    struct Annotation3 {};
    '''

CONSTRUCTOR_BINDING = (
    '',
    '.registerConstructor<XAnnot()>()')
INTERFACE_BINDING = (
    '''
        struct Y : public X {};
    ''',
    '''
        .bind<XAnnot, YAnnot>()
        .registerConstructor<YAnnot()>()
    ''')
INTERFACE_BINDING2 = (
    '''
        struct Y2 : public X {};
    ''',
    '''
        .bind<XAnnot, Y2Annot>()
        .registerConstructor<Y2Annot()>()
    ''')
INSTALL = (
    '''
        fruit::Component<XAnnot> getParentComponent() {
          return fruit::createComponent()
            .registerConstructor<XAnnot()>();
        }
    ''',
    '.install(getParentComponent)')
INSTALL2 = (
    '''
        fruit::Component<XAnnot> getParentComponent2() {
          return fruit::createComponent()
            .registerConstructor<XAnnot()>();
        }
    ''',
    '.install(getParentComponent2)')
CONST_BINDING_FROM_INSTALL = (
    '''
        fruit::Component<const XAnnot> getParentComponent() {
          return fruit::createComponent()
            .registerConstructor<XAnnot()>();
        }
    ''',
    '.install(getParentComponent)')
CONST_BINDING_FROM_INSTALL2 = (
    '''
        fruit::Component<const XAnnot> getParentComponent2() {
          return fruit::createComponent()
            .registerConstructor<XAnnot()>();
        }
    ''',
    '.install(getParentComponent2)')
CONST_BINDING = (
    '''
        const X x{};
    ''',
    '.bindInstance<XAnnot, X>(x)')
CONST_BINDING2 = (
    '''
        const X x2{};
    ''',
    '.bindInstance<XAnnot, X>(x2)')

class TestBindingClash(parameterized.TestCase):
    @multiple_named_parameters([
        ('CONSTRUCTOR_BINDING + INSTALL',) + CONSTRUCTOR_BINDING + INSTALL,
        ('INTERFACE_BINDING + INSTALL',) + INTERFACE_BINDING + INSTALL,
        ('INSTALL + INSTALL2',) + INSTALL + INSTALL2,
        ('CONSTRUCTOR_BINDING + CONST_BINDING_FROM_INSTALL',) + CONSTRUCTOR_BINDING + CONST_BINDING_FROM_INSTALL,
        ('INTERFACE_BINDING + CONST_BINDING_FROM_INSTALL',) + INTERFACE_BINDING + CONST_BINDING_FROM_INSTALL,
        ('INSTALL2 + CONST_BINDING_FROM_INSTALL',) + INSTALL2 + CONST_BINDING_FROM_INSTALL,
        ('CONST_BINDING_FROM_INSTALL + INSTALL2',) + CONST_BINDING_FROM_INSTALL + INSTALL2,
        ('CONST_BINDING + INSTALL2',) + CONST_BINDING + INSTALL2,
        ('CONST_BINDING_FROM_INSTALL + CONST_BINDING_FROM_INSTALL2',) + CONST_BINDING_FROM_INSTALL + CONST_BINDING_FROM_INSTALL2,
        ('CONST_BINDING + CONST_BINDING_FROM_INSTALL',) + CONST_BINDING + CONST_BINDING_FROM_INSTALL,
    ], [
        ('No annotation', 'X', 'Y', 'Y2'),
        ('With annotation', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation3, Y2>'),
    ])
    def test_clash_with_install(self,
            binding1_preparation, binding1, binding2_preparation, binding2, XAnnot, YAnnot, Y2Annot):
        source = '''
            struct X{};
    
            %s
            %s
    
            fruit::Component<XAnnot> getComponent() {
              return fruit::createComponent()
                  %s
                  %s;
            }
            ''' % (binding1_preparation, binding2_preparation, binding1, binding2)
        expect_compile_error(
            'DuplicateTypesInComponentError<XAnnot>',
            'The installed component provides some types that are already provided by the current component.',
            COMMON_DEFINITIONS,
            source,
            locals())

    @multiple_named_parameters([
        ('CONSTRUCTOR_BINDING + CONSTRUCTOR_BINDING',) + CONSTRUCTOR_BINDING + CONSTRUCTOR_BINDING,
        ('CONSTRUCTOR_BINDING + INTERFACE_BINDING',) + CONSTRUCTOR_BINDING + INTERFACE_BINDING,
        ('INTERFACE_BINDING + CONSTRUCTOR_BINDING',) + INTERFACE_BINDING + CONSTRUCTOR_BINDING,
        ('INTERFACE_BINDING + INTERFACE_BINDING2',) + INTERFACE_BINDING + INTERFACE_BINDING2,
        ('INSTALL + CONSTRUCTOR_BINDING',) + INSTALL + CONSTRUCTOR_BINDING,
        ('INSTALL + INTERFACE_BINDING',) + INSTALL + INTERFACE_BINDING,
        ('CONST_BINDING_FROM_INSTALL + CONSTRUCTOR_BINDING',) + CONST_BINDING_FROM_INSTALL + CONSTRUCTOR_BINDING,
        ('CONST_BINDING_FROM_INSTALL + INTERFACE_BINDING',) + CONST_BINDING_FROM_INSTALL + INTERFACE_BINDING,
        ('CONST_BINDING + CONSTRUCTOR_BINDING',) + CONST_BINDING + CONSTRUCTOR_BINDING,
        ('CONST_BINDING + INTERFACE_BINDING',) + CONST_BINDING + INTERFACE_BINDING,
        ('CONSTRUCTOR_BINDING + CONST_BINDING',) + CONSTRUCTOR_BINDING + CONST_BINDING,
        ('INTERFACE_BINDING + CONST_BINDING',) + INTERFACE_BINDING + CONST_BINDING,
        ('INSTALL2 + CONST_BINDING',) + INSTALL2 + CONST_BINDING,
        ('CONST_BINDING_FROM_INSTALL + CONST_BINDING',) + CONST_BINDING_FROM_INSTALL + CONST_BINDING,
        ('CONST_BINDING + CONST_BINDING2',) + CONST_BINDING + CONST_BINDING2,
    ], [
        ('No annotation', 'X', 'Y', 'Y2'),
        ('With annotation', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation3, Y2>'),
    ])
    def test_clash_with_binding(self, binding1_preparation, binding1, binding2_preparation, binding2, XAnnot, YAnnot, Y2Annot):
        source = '''
            struct X{};
    
            %s
            %s
    
            fruit::Component<XAnnot> getComponent() {
              return fruit::createComponent()
                  %s
                  %s;
            }
    
            ''' % (binding1_preparation, binding2_preparation, binding1, binding2)
        expect_compile_error(
            'TypeAlreadyBoundError<XAnnot>',
            'Trying to bind C but it is already bound.',
            COMMON_DEFINITIONS,
            source,
            locals())

    CONSTRUCTOR_BINDING_ANNOT1 = (
        '',
        '.registerConstructor<XAnnot1()>()')
    CONSTRUCTOR_BINDING_ANNOT2 = (
        '',
        '.registerConstructor<XAnnot2()>()')
    INTERFACE_BINDING_ANNOT1 = (
        '''
            struct Y : public X {};
        ''',
        '''
            .bind<XAnnot1, YAnnot1>()
            .registerConstructor<YAnnot1()>()
        ''')
    INTERFACE_BINDING_ANNOT2 = (
        '''
            struct Z : public X {};
        ''',
        '''
            .bind<XAnnot2, ZAnnot2>()
            .registerConstructor<ZAnnot2()>()
        ''')
    INSTALL_ANNOT1 = (
        '''
            fruit::Component<XAnnot1> getParentComponent1() {
              return fruit::createComponent()
                .registerConstructor<XAnnot1()>();
            }
        ''',
        '.install(getParentComponent1)')
    INSTALL_ANNOT2 = (
        '''
            fruit::Component<XAnnot2> getParentComponent2() {
              return fruit::createComponent()
                .registerConstructor<XAnnot2()>();
            }
        ''',
        '.install(getParentComponent2)')
    CONST_BINDING_FROM_INSTALL_ANNOT1 = (
        '''
            fruit::Component<ConstXAnnot1> getParentComponent1() {
              return fruit::createComponent()
                .registerConstructor<XAnnot1()>();
            }
        ''',
        '.install(getParentComponent1)')
    CONST_BINDING_FROM_INSTALL_ANNOT2 = (
        '''
            fruit::Component<ConstXAnnot2> getParentComponent2() {
              return fruit::createComponent()
                .registerConstructor<XAnnot2()>();
            }
        ''',
        '.install(getParentComponent2)')
    CONST_BINDING_ANNOT1 = (
        '''
            const X x1{};
        ''',
        '.bindInstance<XAnnot1, X>(x1)')
    CONST_BINDING_ANNOT2 = (
        '''
            const X x2{};
        ''',
        '.bindInstance<XAnnot2, X>(x2)')

    @parameterized.named_parameters([
        ('CONSTRUCTOR_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2',) + CONSTRUCTOR_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2,
        ('CONSTRUCTOR_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2',) + CONSTRUCTOR_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2,
        ('INTERFACE_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2',) + INTERFACE_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2,
        ('INTERFACE_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2',) + INTERFACE_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2,
        ('INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2',) + INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2,
        ('INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2',) + INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2,
        ('CONST_BINDING_FROM_INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2',) + CONST_BINDING_FROM_INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2,
        ('CONST_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2',) + CONST_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2,
        ('CONST_BINDING_FROM_INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2',) + CONST_BINDING_FROM_INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2,
        ('CONST_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2',) + CONST_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2,
        ('CONSTRUCTOR_BINDING_ANNOT1 + INSTALL_ANNOT2',) + CONSTRUCTOR_BINDING_ANNOT1 + INSTALL_ANNOT2,
        ('INTERFACE_BINDING_ANNOT1 + INSTALL_ANNOT2',) + INTERFACE_BINDING_ANNOT1 + INSTALL_ANNOT2,
        ('CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2',) + CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2,
        ('CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_ANNOT2',) + CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_ANNOT2,
        ('INTERFACE_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2',) + INTERFACE_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2,
        ('INTERFACE_BINDING_ANNOT1 + CONST_BINDING_ANNOT2',) + INTERFACE_BINDING_ANNOT1 + CONST_BINDING_ANNOT2,
        ('INSTALL_ANNOT1 + INSTALL_ANNOT2',) + INSTALL_ANNOT1 + INSTALL_ANNOT2,
        ('CONST_BINDING_FROM_INSTALL_ANNOT1 + INSTALL_ANNOT2',) + CONST_BINDING_FROM_INSTALL_ANNOT1 + INSTALL_ANNOT2,
        ('CONST_BINDING_ANNOT1 + INSTALL_ANNOT2',) + CONST_BINDING_ANNOT1 + INSTALL_ANNOT2,
        ('INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2',) + INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2,
        ('INSTALL_ANNOT1 + CONST_BINDING_ANNOT2',) + INSTALL_ANNOT1 + CONST_BINDING_ANNOT2,
        ('CONST_BINDING_FROM_INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2',) + CONST_BINDING_FROM_INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2,
        ('CONST_BINDING_ANNOT1 + CONST_BINDING_ANNOT2',) + CONST_BINDING_ANNOT1 + CONST_BINDING_ANNOT2,
        ('CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2',) + CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2,
    ])
    def test_no_clash_with_different_annotations(self, binding1_preparation, binding1, binding2_preparation, binding2):
        source = '''
            struct X {};
    
            %s
            %s
    
            fruit::Component<const XAnnot1, const XAnnot2> getComponent() {
              return fruit::createComponent()
                  %s
                  %s;
            }
    
            int main() {
                fruit::Injector<const XAnnot1, const XAnnot2> injector(getComponent);
                injector.get<XAnnot1>();
                injector.get<XAnnot2>();
            }
            ''' % (binding1_preparation, binding2_preparation, binding1, binding2)
        expect_success(
            COMMON_DEFINITIONS,
            source)

    @parameterized.parameters([
        ('X', 'X', 'X'),
        ('const X', 'X', 'X'),
        ('X', 'const X', 'X'),
        ('const X', 'const X', 'X'),
        ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'),
        ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'),
        ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'),
        ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'),
    ])
    def test_during_component_merge(self, NormalizedComponentXAnnot, ComponentXAnnot, XAnnot):
        source = '''
            struct X {};
    
            fruit::Component<NormalizedComponentXAnnot> getComponent1() {
              return fruit::createComponent()
                .registerConstructor<XAnnot()>();
            }
    
            fruit::Component<ComponentXAnnot> getComponent2() {
              return fruit::createComponent()
                .registerConstructor<XAnnot()>();
            }
    
            void f() {
              fruit::NormalizedComponent<NormalizedComponentXAnnot> nc(getComponent1);
              fruit::Injector<> injector(nc, getComponent2);
              (void) injector;
            }
            '''
        expect_compile_error(
            'DuplicateTypesInComponentError<XAnnot>',
            'The installed component provides some types that are already provided',
            COMMON_DEFINITIONS,
            source,
            locals())

    def test_during_component_merge_with_different_annotation_ok(self):
        source = '''
            struct X {};
    
            fruit::Component<XAnnot1> getComponent1() {
              return fruit::createComponent()
                .registerConstructor<XAnnot1()>();
            }
    
            fruit::Component<XAnnot2> getComponent2() {
              return fruit::createComponent()
                .registerConstructor<XAnnot2()>();
            }
    
            int main() {
              fruit::NormalizedComponent<XAnnot1> nc(getComponent1);
              fruit::Injector<XAnnot1, XAnnot2> injector(nc, getComponent2);
              injector.get<XAnnot1>();
              injector.get<XAnnot2>();
            }
            '''
        expect_success(
            COMMON_DEFINITIONS,
            source)

    @parameterized.parameters([
        ('X', '(struct )?X'),
        ('fruit::Annotated<Annotation1, X>', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'),
    ])
    def test_bind_instance_and_bind_instance_runtime(self, XAnnot, XAnnotRegex):
        source = '''
            struct X {};
    
            fruit::Component<> getComponentForInstanceHelper() {
              // Note: don't do this in real code, leaks memory.
              return fruit::createComponent()
                .bindInstance<XAnnot, X>(*(new X()));
            }
            
            fruit::Component<XAnnot> getComponentForInstance() {
              // Note: don't do this in real code, leaks memory.
              return fruit::createComponent()
                .install(getComponentForInstanceHelper)
                .bindInstance<XAnnot, X>(*(new X()));
            }
    
            int main() {
              fruit::Injector<XAnnot> injector(getComponentForInstance);
              injector.get<XAnnot>();
            }
            '''
        expect_runtime_error(
            'Fatal injection error: the type XAnnotRegex was provided more than once, with different bindings.',
            COMMON_DEFINITIONS,
            source,
            locals())

    @parameterized.parameters([
        ('X', '(struct )?X'),
        ('fruit::Annotated<Annotation1, X>', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'),
    ])
    def test_bind_instance_and_binding_runtime(self, XAnnot, XAnnotRegex):
        source = '''
            struct X {};
    
            fruit::Component<> getComponentForInstanceHelper(X* x) {
              return fruit::createComponent()
                .bindInstance<XAnnot, X>(*x);
            }
            
            fruit::Component<XAnnot> getComponentForInstance(X* x) {
              return fruit::createComponent()
                .install(getComponentForInstanceHelper, x)
                .registerConstructor<XAnnot()>();
            }
    
            int main() {
              X x;
              fruit::Injector<XAnnot> injector(getComponentForInstance, &x);
              injector.get<XAnnot>();
            }
            '''
        expect_runtime_error(
            'Fatal injection error: the type XAnnotRegex was provided more than once, with different bindings.',
            COMMON_DEFINITIONS,
            source,
            locals())

    @parameterized.parameters([
        'X',
        'fruit::Annotated<Annotation1, X>',
    ])
    def test_during_component_merge_consistent_ok(self, XAnnot):
        source = '''
            struct X : public ConstructionTracker<X> {
              using Inject = X();
            };
    
            fruit::Component<XAnnot> getComponent() {
              return fruit::createComponent();
            }
    
            fruit::Component<> getRootComponent() {
              return fruit::createComponent()
                  .install(getComponent);
            }
    
            int main() {
              fruit::NormalizedComponent<> normalizedComponent(getRootComponent);
              fruit::Injector<XAnnot> injector(normalizedComponent, getComponent);
    
              Assert(X::num_objects_constructed == 0);
              injector.get<XAnnot>();
              Assert(X::num_objects_constructed == 1);
            }
            '''
        expect_success(
            COMMON_DEFINITIONS,
            source,
            locals())

if __name__ == '__main__':
    absltest.main()