/*
 * 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.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
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.service.ServiceImporter;
import org.qi4j.api.structure.Layer;
import org.qi4j.bootstrap.ApplicationAssembly;
import org.qi4j.bootstrap.AssemblyVisitor;
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.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.Specification;

/**
 * Assembly of a Layer. From here you can create more ModuleAssemblies for
 * the Layer that is being assembled. It is also here that you define
 * what other Layers this Layer is using by calling {@link org.qi4j.runtime.bootstrap.LayerAssemblyImpl#uses()}.
 */
public final class LayerAssemblyImpl
    implements LayerAssembly
{
    private final ApplicationAssembly applicationAssembly;
    private final HashMap<String, ModuleAssemblyImpl> moduleAssemblies;
    private final Set<LayerAssembly> uses;

    private String name;
    private final MetaInfo metaInfo = new MetaInfo();
    private final List<Class<? extends Activator<Layer>>> activators = new ArrayList<>();

    public LayerAssemblyImpl( ApplicationAssembly applicationAssembly, String name )
    {
        this.applicationAssembly = applicationAssembly;
        this.name = name;

        moduleAssemblies = new LinkedHashMap<>();
        uses = new LinkedHashSet<>();
    }

    @Override
    public ModuleAssembly module( String name )
    {
        if( name != null )
        {
            ModuleAssemblyImpl existing = moduleAssemblies.get( name );
            if( existing != null )
            {
                return existing;
            }
        }
        ModuleAssemblyImpl moduleAssembly = new ModuleAssemblyImpl( this, name );
        moduleAssemblies.put( name, moduleAssembly );
        return moduleAssembly;
    }

    @Override
    public ApplicationAssembly application()
    {
        return applicationAssembly;
    }

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

    @Override
    public LayerAssembly setMetaInfo( Object info )
    {
        metaInfo.set( info );
        return this;
    }

    @Override
    public LayerAssembly uses( LayerAssembly... layerAssembly )
        throws IllegalArgumentException
    {
        uses.addAll( Arrays.asList( layerAssembly ) );
        return this;
    }

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

    @Override
    public <ThrowableType extends Throwable> void visit( AssemblyVisitor<ThrowableType> visitor )
        throws ThrowableType
    {
        visitor.visitLayer( this );
        for( ModuleAssemblyImpl moduleAssembly : moduleAssemblies.values() )
        {
            moduleAssembly.visit( visitor );
        }
    }

    @Override
    public EntityDeclaration entities( Specification<? super EntityAssembly> specification )
    {
        final List<EntityDeclaration> declarations = new ArrayList<>();

        for( ModuleAssemblyImpl moduleAssembly : moduleAssemblies.values() )
        {
            declarations.add( moduleAssembly.entities( specification ) );
        }

        return new EntityDeclaration()
        {
            @Override
            public EntityDeclaration setMetaInfo( Object info )
            {
                for( EntityDeclaration declaration : declarations )
                {
                    declaration.setMetaInfo( info );
                }
                return this;
            }

            @Override
            public EntityDeclaration visibleIn( Visibility visibility )
            {
                for( EntityDeclaration declaration : declarations )
                {
                    declaration.visibleIn( visibility );
                }
                return this;
            }

            @Override
            public EntityDeclaration withConcerns( Class<?>... concerns )
            {
                for( EntityDeclaration declaration : declarations )
                {
                    declaration.withConcerns( concerns );
                }
                return this;
            }

            @Override
            public EntityDeclaration withSideEffects( Class<?>... sideEffects )
            {
                for( EntityDeclaration declaration : declarations )
                {
                    declaration.withSideEffects( sideEffects );
                }
                return this;
            }

            @Override
            public EntityDeclaration withMixins( Class<?>... mixins )
            {
                for( EntityDeclaration declaration : declarations )
                {
                    declaration.withMixins( mixins );
                }
                return this;
            }

            @Override
            public EntityDeclaration withTypes( Class<?>... types )
            {
                for( EntityDeclaration declaration : declarations )
                {
                    declaration.withTypes( types );
                }
                return this;
            }
        };
    }

    @Override
    public ServiceDeclaration services( Specification<? super ServiceAssembly> specification )
    {
        final List<ServiceDeclaration> declarations = new ArrayList<>();

        for( ModuleAssemblyImpl moduleAssembly : moduleAssemblies.values() )
        {
            declarations.add( moduleAssembly.services( specification ) );
        }

        return new ServiceDeclaration()
        {
            @Override
            public ServiceDeclaration setMetaInfo( Object serviceAttribute )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.setMetaInfo( serviceAttribute );
                }
                return this;
            }

            @Override
            public ServiceDeclaration visibleIn( Visibility visibility )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.visibleIn( visibility );
                }
                return this;
            }

            @Override
            public ServiceDeclaration withConcerns( Class<?>... concerns )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.withConcerns( concerns );
                }
                return this;
            }

            @Override
            public ServiceDeclaration withSideEffects( Class<?>... sideEffects )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.withSideEffects( sideEffects );
                }
                return this;
            }

            @Override
            public ServiceDeclaration withMixins( Class<?>... mixins )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.withMixins( mixins );
                }
                return this;
            }

            @Override
            public ServiceDeclaration withTypes( Class<?>... types )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.withTypes( types );
                }
                return this;
            }

            @Override
            @SafeVarargs
            public final ServiceDeclaration withActivators( Class<? extends Activator<?>>... activators )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.withActivators( activators );
                }
                return this;
            }

            @Override
            public ServiceDeclaration identifiedBy( String identity )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.identifiedBy( identity );
                }
                return this;
            }

            @Override
            public ServiceDeclaration taggedWith( String... tags )
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.taggedWith( tags );
                }
                return this;
            }

            @Override
            public ServiceDeclaration instantiateOnStartup()
            {
                for( ServiceDeclaration declaration : declarations )
                {
                    declaration.instantiateOnStartup();
                }

                return this;
            }
        };
    }

    @Override
    public TransientDeclaration transients( Specification<? super TransientAssembly> specification )
    {
        final List<TransientDeclaration> declarations = new ArrayList<>();

        for( ModuleAssemblyImpl moduleAssembly : moduleAssemblies.values() )
        {
            declarations.add( moduleAssembly.transients( specification ) );
        }

        return new TransientDeclaration()
        {
            @Override
            public TransientDeclaration setMetaInfo( Object info )
            {
                for( TransientDeclaration declaration : declarations )
                {
                    declaration.setMetaInfo( info );
                }
                return this;
            }

            @Override
            public TransientDeclaration visibleIn( Visibility visibility )
            {
                for( TransientDeclaration declaration : declarations )
                {
                    declaration.visibleIn( visibility );
                }
                return this;
            }

            @Override
            public TransientDeclaration withConcerns( Class<?>... concerns )
            {
                for( TransientDeclaration declaration : declarations )
                {
                    declaration.withConcerns( concerns );
                }
                return this;
            }

            @Override
            public TransientDeclaration withSideEffects( Class<?>... sideEffects )
            {
                for( TransientDeclaration declaration : declarations )
                {
                    declaration.withSideEffects( sideEffects );
                }
                return this;
            }

            @Override
            public TransientDeclaration withMixins( Class<?>... mixins )
            {
                for( TransientDeclaration declaration : declarations )
                {
                    declaration.withMixins( mixins );
                }
                return this;
            }

            @Override
            public TransientDeclaration withTypes( Class<?>... types )
            {
                for( TransientDeclaration declaration : declarations )
                {
                    declaration.withTypes( types );
                }
                return this;
            }
        };
    }

    @Override
    public ValueDeclaration values( Specification<? super ValueAssembly> specification )
    {
        final List<ValueDeclaration> declarations = new ArrayList<>();

        for( ModuleAssemblyImpl moduleAssembly : moduleAssemblies.values() )
        {
            declarations.add( moduleAssembly.values( specification ) );
        }
        return new ValueDeclaration()
        {
            @Override
            public ValueDeclaration setMetaInfo( Object info )
            {
                for( ValueDeclaration declaration : declarations )
                {
                    declaration.setMetaInfo( info );
                }
                return this;
            }

            @Override
            public ValueDeclaration visibleIn( Visibility visibility )
            {
                for( ValueDeclaration declaration : declarations )
                {
                    declaration.visibleIn( visibility );
                }
                return this;
            }

            @Override
            public ValueDeclaration withConcerns( Class<?>... concerns )
            {
                for( ValueDeclaration declaration : declarations )
                {
                    declaration.withConcerns( concerns );
                }
                return this;
            }

            @Override
            public ValueDeclaration withSideEffects( Class<?>... sideEffects )
            {
                for( ValueDeclaration declaration : declarations )
                {
                    declaration.withSideEffects( sideEffects );
                }
                return this;
            }

            @Override
            public ValueDeclaration withMixins( Class<?>... mixins )
            {
                for( ValueDeclaration declaration : declarations )
                {
                    declaration.withMixins( mixins );
                }
                return this;
            }

            @Override
            public ValueDeclaration withTypes( Class<?>... types )
            {
                for( ValueDeclaration declaration : declarations )
                {
                    declaration.withTypes( types );
                }
                return this;
            }
        };
    }

    @Override
    public ObjectDeclaration objects( Specification<? super ObjectAssembly> specification )
    {
        final List<ObjectDeclaration> declarations = new ArrayList<>();

        for( ModuleAssemblyImpl moduleAssembly : moduleAssemblies.values() )
        {
            declarations.add( moduleAssembly.objects( specification ) );
        }
        return new ObjectDeclaration()
        {
            @Override
            public ObjectDeclaration setMetaInfo( Object info )
            {
                for( ObjectDeclaration declaration : declarations )
                {
                    declaration.setMetaInfo( info );
                }
                return this;
            }

            @Override
            public ObjectDeclaration visibleIn( Visibility visibility )
                throws IllegalStateException
            {
                for( ObjectDeclaration declaration : declarations )
                {
                    declaration.visibleIn( visibility );
                }
                return this;
            }
        };
    }

    @Override
    public ImportedServiceDeclaration importedServices( Specification<? super ImportedServiceAssembly> specification )
    {
        final List<ImportedServiceDeclaration> declarations = new ArrayList<>();

        for( ModuleAssemblyImpl moduleAssembly : moduleAssemblies.values() )
        {
            declarations.add( moduleAssembly.importedServices( specification ) );
        }
        return new ImportedServiceDeclaration()
        {

            @Override
            public ImportedServiceDeclaration importOnStartup()
            {
                for( ImportedServiceDeclaration declaration : declarations )
                {
                    declaration.importOnStartup();
                }
                return this;
            }

            @Override
            public ImportedServiceDeclaration visibleIn( Visibility visibility )
            {
                for( ImportedServiceDeclaration declaration : declarations )
                {
                    declaration.visibleIn( visibility );
                }
                return this;
            }

            @Override
            public ImportedServiceDeclaration importedBy( Class<? extends ServiceImporter> serviceImporterClass )
            {
                for( ImportedServiceDeclaration declaration : declarations )
                {
                    declaration.importedBy( serviceImporterClass );
                }
                return this;
            }

            @Override
            public ImportedServiceDeclaration identifiedBy( String identity )
            {
                for( ImportedServiceDeclaration declaration : declarations )
                {
                    declaration.identifiedBy( identity );
                }
                return this;
            }

            @Override
            public ImportedServiceDeclaration taggedWith( String... tags )
            {
                for( ImportedServiceDeclaration declaration : declarations )
                {
                    declaration.taggedWith( tags );
                }
                return this;
            }

            @Override
            public ImportedServiceDeclaration setMetaInfo( Object serviceAttribute )
            {
                for( ImportedServiceDeclaration declaration : declarations )
                {
                    declaration.setMetaInfo( serviceAttribute );
                }
                return this;
            }

            @Override
            @SafeVarargs
            public final ImportedServiceDeclaration withActivators( Class<? extends Activator<?>>... activators )
            {
                for( ImportedServiceDeclaration declaration : declarations )
                {
                    declaration.withActivators( activators );
                }
                return this;
            }

        };
    }

    Collection<ModuleAssemblyImpl> moduleAssemblies()
    {
        return moduleAssemblies.values();
    }

    Set<LayerAssembly> uses()
    {
        return uses;
    }

    public MetaInfo metaInfo()
    {
        return metaInfo;
    }

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

    public List<Class<? extends Activator<Layer>>> activators()
    {
        return activators;
    }

    @Override
    public final String toString()
    {
        return "LayerAssembly [" + name + "]";
    }
}
