# ModuleAssemblyImpl.java

    /*
     * Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
     * Copyright (c) 2012, Paul Merlin.
     *
     * 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.qi4j.runtime.bootstrap;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import org.qi4j.api.activation.Activator;
    import org.qi4j.api.common.MetaInfo;
    import org.qi4j.api.common.Visibility;
    import org.qi4j.api.composite.TransientComposite;
    import org.qi4j.api.entity.EntityComposite;
    import org.qi4j.api.entity.Identity;
    import org.qi4j.api.service.DuplicateServiceIdentityException;
    import org.qi4j.api.service.ServiceImporter;
    import org.qi4j.api.structure.Module;
    import org.qi4j.api.type.HasTypes;
    import org.qi4j.api.type.MatchTypeSpecification;
    import org.qi4j.api.value.ValueComposite;
    import org.qi4j.bootstrap.AssemblyException;
    import org.qi4j.bootstrap.AssemblySpecifications;
    import org.qi4j.bootstrap.AssemblyVisitor;
    import org.qi4j.bootstrap.ConfigurationDeclaration;
    import org.qi4j.bootstrap.EntityAssembly;
    import org.qi4j.bootstrap.EntityDeclaration;
    import org.qi4j.bootstrap.ImportedServiceAssembly;
    import org.qi4j.bootstrap.ImportedServiceDeclaration;
    import org.qi4j.bootstrap.LayerAssembly;
    import org.qi4j.bootstrap.MetaInfoDeclaration;
    import org.qi4j.bootstrap.MixinDeclaration;
    import org.qi4j.bootstrap.ModuleAssembly;
    import org.qi4j.bootstrap.ObjectAssembly;
    import org.qi4j.bootstrap.ObjectDeclaration;
    import org.qi4j.bootstrap.ServiceAssembly;
    import org.qi4j.bootstrap.ServiceDeclaration;
    import org.qi4j.bootstrap.TransientAssembly;
    import org.qi4j.bootstrap.TransientDeclaration;
    import org.qi4j.bootstrap.ValueAssembly;
    import org.qi4j.bootstrap.ValueDeclaration;
    import org.qi4j.functional.Iterables;
    import org.qi4j.functional.Specification;
    import org.qi4j.functional.Specifications;
    import org.qi4j.runtime.activation.ActivatorsModel;
    import org.qi4j.runtime.composite.TransientModel;
    import org.qi4j.runtime.composite.TransientsModel;
    import org.qi4j.runtime.entity.EntitiesModel;
    import org.qi4j.runtime.entity.EntityModel;
    import org.qi4j.runtime.object.ObjectModel;
    import org.qi4j.runtime.object.ObjectsModel;
    import org.qi4j.runtime.service.ImportedServiceModel;
    import org.qi4j.runtime.service.ImportedServicesModel;
    import org.qi4j.runtime.service.ServiceModel;
    import org.qi4j.runtime.service.ServicesModel;
    import org.qi4j.runtime.structure.ModuleModel;
    import org.qi4j.runtime.value.ValueModel;
    import org.qi4j.runtime.value.ValuesModel;

    import static org.qi4j.functional.Iterables.first;
    import static org.qi4j.functional.Iterables.iterable;

    /**
     * Assembly of a Module. This is where you register all objects, Composites,
     * Services. Each "add" method returns a declaration that you can use to add
     * additional information and metadata. If you call an "add" method with many
     * parameters then the declared metadata will apply to all types in the method
     * call.
     */
    public final class ModuleAssemblyImpl
        implements ModuleAssembly
    {
        private final LayerAssembly layerAssembly;
        private String name;
        private final MetaInfo metaInfo = new MetaInfo();
        private final List<Class<? extends Activator<Module>>> activators = new ArrayList<>();

        private final List<ServiceAssemblyImpl> serviceAssemblies = new ArrayList<>();
        private final Map<Class<?>, ImportedServiceAssemblyImpl> importedServiceAssemblies = new LinkedHashMap<>();
        private final Map<Class<? extends EntityComposite>, EntityAssemblyImpl> entityAssemblies = new LinkedHashMap<>();
        private final Map<Class<? extends ValueComposite>, ValueAssemblyImpl> valueAssemblies = new LinkedHashMap<>();
        private final Map<Class<? extends TransientComposite>, TransientAssemblyImpl> transientAssemblies = new LinkedHashMap<>();
        private final Map<Class<?>, ObjectAssemblyImpl> objectAssemblies = new LinkedHashMap<>();

        private final MetaInfoDeclaration metaInfoDeclaration = new MetaInfoDeclaration();

        public ModuleAssemblyImpl( LayerAssembly layerAssembly, String name )
        {
            this.layerAssembly = layerAssembly;
            this.name = name;
        }

        @Override
        public LayerAssembly layer()
        {
            return layerAssembly;
        }

        @Override
        public ModuleAssembly module( String layerName, String moduleName )
        {
            return layerAssembly.application().module( layerName, moduleName );
        }

        @Override
        public ModuleAssembly setName( String name )
        {
            this.name = name;
            return this;
        }

        @Override
        public String name()
        {
            return name;
        }

        public ModuleAssembly setMetaInfo( Object info )
        {
            metaInfo.set( info );
            return this;
        }

        @Override
        @SafeVarargs
        public final ModuleAssembly withActivators( Class<? extends Activator<Module>>... activators )
        {
            this.activators.addAll( Arrays.asList( activators ) );
            return this;
        }

        @Override
        @SuppressWarnings( {"raw", "unchecked"} )
        public ValueDeclaration values( Class<?>... valueTypes )
        {
            List<ValueAssemblyImpl> assemblies = new ArrayList<>();

            for( Class valueType : valueTypes )
            {
                if( valueAssemblies.containsKey( valueType ) )
                {
                    assemblies.add( valueAssemblies.get( valueType ) );
                }
                else
                {
                    ValueAssemblyImpl valueAssembly = new ValueAssemblyImpl( valueType );
                    valueAssemblies.put( valueType, valueAssembly );
                    assemblies.add( valueAssembly );
                }
            }

            return new ValueDeclarationImpl( assemblies );
        }

        @Override
        public ValueDeclaration values( Specification<? super ValueAssembly> specification )
        {
            List<ValueAssemblyImpl> assemblies = new ArrayList<>();
            for( ValueAssemblyImpl transientAssembly : valueAssemblies.values() )
            {
                if( specification.satisfiedBy( transientAssembly ) )
                {
                    assemblies.add( transientAssembly );
                }
            }

            return new ValueDeclarationImpl( assemblies );
        }

        @Override
        @SuppressWarnings( {"raw", "unchecked"} )
        public TransientDeclaration transients( Class<?>... transientTypes )
        {
            List<TransientAssemblyImpl> assemblies = new ArrayList<>();

            for( Class valueType : transientTypes )
            {
                if( transientAssemblies.containsKey( valueType ) )
                {
                    assemblies.add( transientAssemblies.get( valueType ) );
                }
                else
                {
                    TransientAssemblyImpl transientAssembly = new TransientAssemblyImpl( valueType );
                    transientAssemblies.put( valueType, transientAssembly );
                    assemblies.add( transientAssembly );
                }
            }

            return new TransientDeclarationImpl( assemblies );
        }

        @Override
        public TransientDeclaration transients( Specification<? super TransientAssembly> specification )
        {
            List<TransientAssemblyImpl> assemblies = new ArrayList<>();
            for( TransientAssemblyImpl transientAssembly : transientAssemblies.values() )
            {
                if( specification.satisfiedBy( transientAssembly ) )
                {
                    assemblies.add( transientAssembly );
                }
            }

            return new TransientDeclarationImpl( assemblies );
        }

        @Override
        @SuppressWarnings( {"raw", "unchecked"} )
        public EntityDeclaration entities( Class<?>... entityTypes )
        {
            List<EntityAssemblyImpl> assemblies = new ArrayList<>();

            for( Class entityType : entityTypes )
            {
                if( entityAssemblies.containsKey( entityType ) )
                {
                    assemblies.add( entityAssemblies.get( entityType ) );
                }
                else
                {
                    EntityAssemblyImpl entityAssembly = new EntityAssemblyImpl( entityType );
                    entityAssemblies.put( entityType, entityAssembly );
                    assemblies.add( entityAssembly );
                }
            }

            return new EntityDeclarationImpl( assemblies );
        }

        @Override
        public EntityDeclaration entities( Specification<? super EntityAssembly> specification )
        {
            List<EntityAssemblyImpl> assemblies = new ArrayList<>();
            for( EntityAssemblyImpl entityAssembly : entityAssemblies.values() )
            {
                if( specification.satisfiedBy( entityAssembly ) )
                {
                    assemblies.add( entityAssembly );
                }
            }

            return new EntityDeclarationImpl( assemblies );
        }

        @Override
        public ConfigurationDeclaration configurations( Class<?>... configurationTypes )
        {
            List<EntityAssemblyImpl> entityAssemblyList = new ArrayList<>();

            for( Class entityType : configurationTypes )
            {
                if( this.entityAssemblies.containsKey( entityType ) )
                {
                    entityAssemblyList.add( this.entityAssemblies.get( entityType ) );
                }
                else
                {
                    EntityAssemblyImpl entityAssembly = new EntityAssemblyImpl( entityType );
                    this.entityAssemblies.put( entityType, entityAssembly );
                    entityAssemblyList.add( entityAssembly );
                }
            }

            List<ValueAssemblyImpl> valueAssemblyList = new ArrayList<>();

            for( Class valueType : configurationTypes )
            {
                if( valueAssemblies.containsKey( valueType ) )
                {
                    valueAssemblyList.add( valueAssemblies.get( valueType ) );
                }
                else
                {
                    ValueAssemblyImpl valueAssembly = new ValueAssemblyImpl( valueType );
                    valueAssemblies.put( valueType, valueAssembly );
                    valueAssemblyList.add( valueAssembly );
                    valueAssembly.types.add( Identity.class );
                }
            }

            return new ConfigurationDeclarationImpl( entityAssemblyList, valueAssemblyList  );
        }

        @Override
        public ConfigurationDeclaration configurations( Specification<HasTypes> specification )
        {
            Specification<HasTypes> isConfigurationComposite = new MatchTypeSpecification( Identity.class );
            specification = Specifications.and( specification, isConfigurationComposite );
            List<EntityAssemblyImpl> entityAssmblyList = new ArrayList<>();
            for( EntityAssemblyImpl entityAssembly : entityAssemblies.values() )
            {
                if( specification.satisfiedBy( entityAssembly ) )
                {
                    entityAssmblyList.add( entityAssembly );
                }
            }
            List<ValueAssemblyImpl> valueAssemblyList = new ArrayList<>();
            for( ValueAssemblyImpl transientAssembly : valueAssemblies.values() )
            {
                if( specification.satisfiedBy( transientAssembly ) )
                {
                    valueAssemblyList.add( transientAssembly );
                }
            }
            return new ConfigurationDeclarationImpl( entityAssmblyList, valueAssemblyList );
        }

        @Override
        public ObjectDeclaration objects( Class<?>... objectTypes )
            throws AssemblyException
        {
            List<ObjectAssemblyImpl> assemblies = new ArrayList<>();

            for( Class<?> objectType : objectTypes )
            {
                if( objectType.isInterface() )
                {
                    throw new AssemblyException( "Interfaces can not be Zest Objects." );
                }
                if( objectAssemblies.containsKey( objectType ) )
                {
                    assemblies.add( objectAssemblies.get( objectType ) );
                }
                else
                {
                    ObjectAssemblyImpl objectAssembly = new ObjectAssemblyImpl( objectType );
                    objectAssemblies.put( objectType, objectAssembly );
                    assemblies.add( objectAssembly );
                }
            }

            return new ObjectDeclarationImpl( assemblies );
        }

        @Override
        public ObjectDeclaration objects( Specification<? super ObjectAssembly> specification )
        {
            List<ObjectAssemblyImpl> assemblies = new ArrayList<>();
            for( ObjectAssemblyImpl objectAssembly : objectAssemblies.values() )
            {
                if( specification.satisfiedBy( objectAssembly ) )
                {
                    assemblies.add( objectAssembly );
                }
            }

            return new ObjectDeclarationImpl( assemblies );
        }

        @Override
        public ServiceDeclaration addServices( Class<?>... serviceTypes )
        {
            List<ServiceAssemblyImpl> assemblies = new ArrayList<>();

            for( Class<?> serviceType : serviceTypes )
            {
                ServiceAssemblyImpl serviceAssembly = new ServiceAssemblyImpl( serviceType );
                serviceAssemblies.add( serviceAssembly );
                assemblies.add( serviceAssembly );
            }

            return new ServiceDeclarationImpl( assemblies );
        }

        @Override
        public ServiceDeclaration services( Class<?>... serviceTypes )
        {
            List<ServiceAssemblyImpl> assemblies = new ArrayList<>();

            for( Class<?> serviceType : serviceTypes )
            {
                if( Iterables.matchesAny( AssemblySpecifications.types( serviceType ), serviceAssemblies ) )
                {
                    Iterables.addAll( assemblies, Iterables.filter( AssemblySpecifications.types( serviceType ), serviceAssemblies ) );
                }
                else
                {
                    ServiceAssemblyImpl serviceAssembly = new ServiceAssemblyImpl( serviceType );
                    serviceAssemblies.add( serviceAssembly );
                    assemblies.add( serviceAssembly );
                }
            }

            return new ServiceDeclarationImpl( assemblies );
        }

        @Override
        public ServiceDeclaration services( Specification<? super ServiceAssembly> specification )
        {
            List<ServiceAssemblyImpl> assemblies = new ArrayList<>();
            for( ServiceAssemblyImpl serviceAssembly : serviceAssemblies )
            {
                if( specification.satisfiedBy( serviceAssembly ) )
                {
                    assemblies.add( serviceAssembly );
                }
            }

            return new ServiceDeclarationImpl( assemblies );
        }

        @Override
        public ImportedServiceDeclaration importedServices( Class<?>... serviceTypes )
        {
            List<ImportedServiceAssemblyImpl> assemblies = new ArrayList<>();

            for( Class<?> serviceType : serviceTypes )
            {
                if( importedServiceAssemblies.containsKey( serviceType ) )
                {
                    assemblies.add( importedServiceAssemblies.get( serviceType ) );
                }
                else
                {
                    ImportedServiceAssemblyImpl serviceAssembly = new ImportedServiceAssemblyImpl( serviceType, this );
                    importedServiceAssemblies.put( serviceType, serviceAssembly );
                    assemblies.add( serviceAssembly );
                }
            }

            return new ImportedServiceDeclarationImpl( assemblies );
        }

        @Override
        public ImportedServiceDeclaration importedServices( Specification<? super ImportedServiceAssembly> specification )
        {
            List<ImportedServiceAssemblyImpl> assemblies = new ArrayList<>();
            for( ImportedServiceAssemblyImpl objectAssembly : importedServiceAssemblies.values() )
            {
                if( specification.satisfiedBy( objectAssembly ) )
                {
                    assemblies.add( objectAssembly );
                }
            }

            return new ImportedServiceDeclarationImpl( assemblies );
        }

        @Override
        public <T> MixinDeclaration<T> forMixin( Class<T> mixinType )
        {
            return metaInfoDeclaration.on( mixinType );
        }

        @Override
        public <ThrowableType extends Throwable> void visit( AssemblyVisitor<ThrowableType> visitor )
            throws ThrowableType
        {
            visitor.visitModule( this );

            for( TransientAssemblyImpl compositeDeclaration : transientAssemblies.values() )
            {
                visitor.visitComposite( new TransientDeclarationImpl( iterable( compositeDeclaration ) ) );
            }

            for( EntityAssemblyImpl entityDeclaration : entityAssemblies.values() )
            {
                visitor.visitEntity( new EntityDeclarationImpl( iterable( entityDeclaration ) ) );
            }

            for( ObjectAssemblyImpl objectDeclaration : objectAssemblies.values() )
            {
                visitor.visitObject( new ObjectDeclarationImpl( iterable( objectDeclaration ) ) );
            }

            for( ServiceAssemblyImpl serviceDeclaration : serviceAssemblies )
            {
                visitor.visitService( new ServiceDeclarationImpl( iterable( serviceDeclaration ) ) );
            }

            for( ImportedServiceAssemblyImpl importedServiceDeclaration : importedServiceAssemblies.values() )
            {
                visitor.visitImportedService( new ImportedServiceDeclarationImpl( iterable( importedServiceDeclaration ) ) );
            }

            for( ValueAssemblyImpl valueDeclaration : valueAssemblies.values() )
            {
                visitor.visitValue( new ValueDeclarationImpl( iterable( valueDeclaration ) ) );
            }
        }

        ModuleModel assembleModule( AssemblyHelper helper )
            throws AssemblyException
        {
            List<TransientModel> transientModels = new ArrayList<>();
            List<ObjectModel> objectModels = new ArrayList<>();
            List<ValueModel> valueModels = new ArrayList<>();
            List<ServiceModel> serviceModels = new ArrayList<>();
            List<ImportedServiceModel> importedServiceModels = new ArrayList<>();

            if( name == null )
            {
                throw new AssemblyException( "Module must have name set" );
            }

            for( TransientAssemblyImpl compositeDeclaration : transientAssemblies.values() )
            {
                transientModels.add( compositeDeclaration.newTransientModel( metaInfoDeclaration, helper ) );
            }

            for( ValueAssemblyImpl valueDeclaration : valueAssemblies.values() )
            {
                valueModels.add( valueDeclaration.newValueModel( metaInfoDeclaration, helper ) );
            }

            List<EntityModel> entityModels = new ArrayList<>();
            for( EntityAssemblyImpl entityDeclaration : entityAssemblies.values() )
            {
                entityModels.add( entityDeclaration.newEntityModel( metaInfoDeclaration,
                                                                    metaInfoDeclaration,
                                                                    metaInfoDeclaration,
                                                                    metaInfoDeclaration,
                                                                    helper ) );
            }

            for( ObjectAssemblyImpl objectDeclaration : objectAssemblies.values() )
            {
                objectDeclaration.addObjectModel( objectModels );
            }

            for( ServiceAssemblyImpl serviceDeclaration : serviceAssemblies )
            {
                if( serviceDeclaration.identity == null )
                {
                    serviceDeclaration.identity = generateId( serviceDeclaration.types() );
                }

                serviceModels.add( serviceDeclaration.newServiceModel( metaInfoDeclaration, helper ) );
            }

            for( ImportedServiceAssemblyImpl importedServiceDeclaration : importedServiceAssemblies.values() )
            {
                importedServiceDeclaration.addImportedServiceModel( importedServiceModels );
            }

            ModuleModel moduleModel = new ModuleModel( name,
                                                       metaInfo,
                                                       new ActivatorsModel<>( activators ),
                                                       new TransientsModel( transientModels ),
                                                       new EntitiesModel( entityModels ),
                                                       new ObjectsModel( objectModels ),
                                                       new ValuesModel( valueModels ),
                                                       new ServicesModel( serviceModels ),
                                                       new ImportedServicesModel( importedServiceModels ) );

            // Check for duplicate service identities
            Set<String> identities = new HashSet<>();
            for( ServiceModel serviceModel : serviceModels )
            {
                String identity = serviceModel.identity();
                if( identities.contains( identity ) )
                {
                    throw new DuplicateServiceIdentityException(
                        "Duplicated service identity: " + identity + " in module " + moduleModel.name()
                    );
                }
                identities.add( identity );
            }
            for( ImportedServiceModel serviceModel : importedServiceModels )
            {
                String identity = serviceModel.identity();
                if( identities.contains( identity ) )
                {
                    throw new DuplicateServiceIdentityException(
                        "Duplicated service identity: " + identity + " in module " + moduleModel.name()
                    );
                }
                identities.add( identity );
            }

            for( ImportedServiceModel importedServiceModel : importedServiceModels )
            {
                boolean found = false;
                for( ObjectModel objectModel : objectModels )
                {
                    if( first( objectModel.types() ).equals( importedServiceModel.serviceImporter() ) )
                    {
                        found = true;
                        break;
                    }
                }
                if( !found )
                {
                    @SuppressWarnings( "raw" )
                    Class<? extends ServiceImporter> serviceFactoryType = importedServiceModel.serviceImporter();
                    ObjectModel objectModel = new ObjectModel( serviceFactoryType, Visibility.module, new MetaInfo() );
                    objectModels.add( objectModel );
                }
            }

            return moduleModel;
        }

        private String generateId( Iterable<Class<?>> serviceTypes )
        {
            // Find service identity that is not yet used
            Class<?> serviceType = serviceTypes.iterator()
                .next(); // Use the first Iterable, which *SHOULD* be the main serviceType
            int idx = 0;
            String id = serviceType.getSimpleName();
            boolean invalid;
            do
            {
                invalid = false;
                for( ServiceAssemblyImpl serviceAssembly : serviceAssemblies )
                {
                    if( serviceAssembly.identity() != null && serviceAssembly.identity().equals( id ) )
                    {
                        idx++;
                        id = serviceType.getSimpleName() + "_" + idx;
                        invalid = true;
                        break;
                    }
                }
            }
            while( invalid );
            return id;
        }
    }