/*
 * Copyright (c) 2008, Rickard Öberg. All Rights Reserved.
 * Copyright (c) 2008, Sonny Gill. All Rights Reserved.
 * Copyright (c) 2008, Niclas Hedhman. All Rights Reserved.
 * Copyright (c) 2014-2015, Paul Merlin. 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. 
 */
package org.qi4j.tools.model.descriptor;

import java.util.HashMap;
import java.util.Map;
import org.qi4j.api.activation.ActivatorDescriptor;
import org.qi4j.api.composite.ConstructorDescriptor;
import org.qi4j.api.composite.InjectedFieldDescriptor;
import org.qi4j.api.composite.InjectedMethodDescriptor;
import org.qi4j.api.composite.InjectedParametersDescriptor;
import org.qi4j.api.composite.MethodDescriptor;
import org.qi4j.api.composite.TransientDescriptor;
import org.qi4j.api.concern.ConcernDescriptor;
import org.qi4j.api.concern.ConcernsDescriptor;
import org.qi4j.api.constraint.ConstraintDescriptor;
import org.qi4j.api.constraint.ConstraintsDescriptor;
import org.qi4j.api.entity.EntityDescriptor;
import org.qi4j.api.mixin.MixinDescriptor;
import org.qi4j.api.object.ObjectDescriptor;
import org.qi4j.api.service.ImportedServiceDescriptor;
import org.qi4j.api.service.ServiceDescriptor;
import org.qi4j.api.sideeffect.SideEffectDescriptor;
import org.qi4j.api.sideeffect.SideEffectsDescriptor;
import org.qi4j.api.structure.ApplicationDescriptor;
import org.qi4j.api.structure.LayerDescriptor;
import org.qi4j.api.structure.ModuleDescriptor;
import org.qi4j.api.structure.UsedLayersDescriptor;
import org.qi4j.api.value.ValueDescriptor;
import org.qi4j.functional.HierarchicalVisitor;

public final class ApplicationDetailDescriptorBuilder
{
    private ApplicationDetailDescriptorBuilder()
    {
    }

    public static ApplicationDetailDescriptor createApplicationDetailDescriptor( ApplicationDescriptor anApplication )
    {
        ApplicationDescriptorVisitor visitor = new ApplicationDescriptorVisitor();
        anApplication.accept( visitor );

        return visitor.applicationDescriptor;
    }

    static final class ApplicationDescriptorVisitor
        implements HierarchicalVisitor<Object, Object, RuntimeException>
    {
        // Temp: application
        private ApplicationDetailDescriptor applicationDescriptor;

        // Temp: Layer variables
        private LayerDetailDescriptor currLayerDescriptor;
        // Cache to lookup layer descriptor -> node
        private final Map<LayerDescriptor, LayerDetailDescriptor> layerDescToDetail;

        // Temp: current module
        private ModuleDetailDescriptor currModuleDescriptor;

        // Temp: current composite
        private CompositeDetailDescriptor currCompositeDescriptor;

        // Temp: curr activator
        private ActivatorDetailDescriptor currActivatorDescriptor;

        // Temp: curr mixin
        private MixinDetailDescriptor currMixinDescriptor;

        // Temp: current constructor
        private ConstructorDetailDescriptor currConstructorDescriptor;

        // Temp: current injected method
        private InjectedMethodDetailDescriptor currInjectedMethodDescriptor;

        // Temp: current composite method
        private CompositeMethodDetailDescriptor currMethodDesciptor;

        // Temp: current method constraints
        private MethodConstraintsDetailDescriptor currMethodConstraintsDescriptor;

        // Temp: current object
        private ObjectDetailDescriptor currObjectDescriptor;

        // Temp: current method concerns
        private MethodConcernsDetailDescriptor currMethodConcernsDescriptor;

        // Temp: current method concern
        private MethodConcernDetailDescriptor currMethodConcernDescriptor;

        // Temp: current method side effects
        private MethodSideEffectsDetailDescriptor currMethodSideEffectsDescriptor;

        // Temp: current side effect
        private MethodSideEffectDetailDescriptor currMethodSideEffectDescriptor;

        private ApplicationDescriptorVisitor()
        {
            layerDescToDetail = new HashMap<>();
        }

        @Override
        public boolean visitEnter( Object visited )
            throws RuntimeException
        {
            if( visited instanceof ApplicationDescriptor )
            {
                applicationDescriptor = new ApplicationDetailDescriptor( (ApplicationDescriptor) visited );
            }
            else if( visited instanceof LayerDescriptor )
            {
                LayerDescriptor layerDescriptor = (LayerDescriptor) visited;
                currLayerDescriptor = getLayerDetailDescriptor( layerDescriptor );
                applicationDescriptor.addLayer( currLayerDescriptor );

                UsedLayersDescriptor usedLayesDescriptor = layerDescriptor.usedLayers();
                Iterable<? extends LayerDescriptor> usedLayers = usedLayesDescriptor.layers();
                for( LayerDescriptor usedLayer : usedLayers )
                {
                    LayerDetailDescriptor usedLayerDetailDesc = getLayerDetailDescriptor( usedLayer );
                    currLayerDescriptor.addUsedLayer( usedLayerDetailDesc );
                }
            }
            else if( visited instanceof ModuleDescriptor )
            {
                ModuleDescriptor moduleDescriptor = (ModuleDescriptor) visited;
                currModuleDescriptor = new ModuleDetailDescriptor( moduleDescriptor );
                currLayerDescriptor.addModule( currModuleDescriptor );
            }
            else if( visited instanceof ActivatorDescriptor )
            {
                ActivatorDescriptor activatorDescriptor = (ActivatorDescriptor) visited;
                currActivatorDescriptor = new ActivatorDetailDescriptor( activatorDescriptor );
                if( currCompositeDescriptor != null )
                {
                    if( currCompositeDescriptor instanceof ServiceDetailDescriptor )
                    {
                        ( (ServiceDetailDescriptor) currCompositeDescriptor ).addActivator( currActivatorDescriptor );
                    }
                    else if( currCompositeDescriptor instanceof ImportedServiceDetailDescriptor )
                    {
                        ( (ImportedServiceDetailDescriptor) currCompositeDescriptor ).addActivator( currActivatorDescriptor );
                    }
                    else
                    {
                        throw new IllegalStateException( "ActivatorDescriptor is only valid for "
                                                         + "services, modules, layers and application." );
                    }
                }
                else if( currModuleDescriptor != null )
                {
                    currModuleDescriptor.addActivator( currActivatorDescriptor );
                }
                else if( currLayerDescriptor != null )
                {
                    currLayerDescriptor.addActivator( currActivatorDescriptor );
                }
                else if( applicationDescriptor != null )
                {
                    applicationDescriptor.addActivator( currActivatorDescriptor );
                }
                else
                {
                    throw new IllegalStateException( "ActivatorDescriptor is only valid for "
                                                     + "services, modules, layers and application." );
                }
            }
            else if( visited instanceof ServiceDescriptor )
            {
                ServiceDetailDescriptor descriptor = new ServiceDetailDescriptor( (ServiceDescriptor) visited );
                currModuleDescriptor.addService( descriptor );
                currCompositeDescriptor = descriptor;
            }
            else if( visited instanceof ImportedServiceDescriptor )
            {
                ImportedServiceDetailDescriptor descriptor = new ImportedServiceDetailDescriptor( new ImportedServiceCompositeDescriptor( (ImportedServiceDescriptor) visited ) );
                currModuleDescriptor.addImportedService( descriptor );
                currCompositeDescriptor = descriptor;
            }
            else if( visited instanceof EntityDescriptor )
            {
                EntityDetailDescriptor descriptor = new EntityDetailDescriptor( (EntityDescriptor) visited );
                currModuleDescriptor.addEntity( descriptor );
                currCompositeDescriptor = descriptor;
            }
            else if( visited instanceof ValueDescriptor )
            {
                ValueDetailDescriptor descriptor = new ValueDetailDescriptor( (ValueDescriptor) visited );
                currModuleDescriptor.addValue( descriptor );
                currCompositeDescriptor = descriptor;
            }
            else if( visited instanceof TransientDescriptor )
            {
                TransientDetailDescriptor descriptor = new TransientDetailDescriptor( (TransientDescriptor) visited );
                currModuleDescriptor.addTransient( descriptor );
                currCompositeDescriptor = descriptor;
            }
            else if( visited instanceof MethodDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                currMethodDesciptor = new CompositeMethodDetailDescriptor( (MethodDescriptor) visited );
                currCompositeDescriptor.addMethod( currMethodDesciptor );
            }
            else if( visited instanceof ConstraintsDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                currMethodConstraintsDescriptor = new MethodConstraintsDetailDescriptor( (ConstraintsDescriptor) visited );
                currMethodDesciptor.setConstraints( currMethodConstraintsDescriptor );
            }
            else if( visited instanceof ConcernsDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                currMethodConcernsDescriptor = new MethodConcernsDetailDescriptor( (ConcernsDescriptor) visited );
                currMethodDesciptor.setConcerns( currMethodConcernsDescriptor );
            }
            else if( visited instanceof ConcernDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                resetInjectableRelatedVariables();

                currMethodConcernDescriptor = new MethodConcernDetailDescriptor( (ConcernDescriptor) visited );
                currMethodConcernsDescriptor.addConcern( currMethodConcernDescriptor );
            }
            else if( visited instanceof SideEffectsDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                currMethodSideEffectsDescriptor = new MethodSideEffectsDetailDescriptor( (SideEffectsDescriptor) visited );
                currMethodDesciptor.setSideEffects( currMethodSideEffectsDescriptor );
            }
            else if( visited instanceof SideEffectDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                resetInjectableRelatedVariables();

                currMethodSideEffectDescriptor = new MethodSideEffectDetailDescriptor( (SideEffectDescriptor) visited );
                currMethodSideEffectsDescriptor.addSideEffect( currMethodSideEffectDescriptor );
            }
            else if( visited instanceof MixinDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                resetInjectableRelatedVariables();

                currMixinDescriptor = new MixinDetailDescriptor( (MixinDescriptor) visited );
                currCompositeDescriptor.addMixin( currMixinDescriptor );
            }
            else if( visited instanceof ObjectDescriptor )
            {
                resetInjectableRelatedVariables();

                currObjectDescriptor = new ObjectDetailDescriptor( (ObjectDescriptor) visited );
                currModuleDescriptor.addObject( currObjectDescriptor );
            }
            else if( visited instanceof ConstructorDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                currConstructorDescriptor = new ConstructorDetailDescriptor( (ConstructorDescriptor) visited );
                currInjectedMethodDescriptor = null;

                // Invoked for mixin and object
                if( currActivatorDescriptor != null )
                {
                    currActivatorDescriptor.addConstructor( currConstructorDescriptor );
                }
                else if( currMixinDescriptor != null )
                {
                    currMixinDescriptor.addConstructor( currConstructorDescriptor );
                }
                else if( currObjectDescriptor != null )
                {
                    currObjectDescriptor.addConstructor( currConstructorDescriptor );
                }
                else if( currMethodConcernDescriptor != null )
                {
                    currMethodConcernDescriptor.addConstructor( currConstructorDescriptor );
                }
                else if( currMethodSideEffectDescriptor != null )
                {
                    currMethodSideEffectDescriptor.addConstructor( currConstructorDescriptor );
                }
                else
                {
                    throw new IllegalStateException( "ConstructorDescriptor is only valid for "
                                                     + "activator, mixin, object, concern and side-effect. "
                                                     + "Visiting [" + visited + "]" );
                }
            }
            else if( visited instanceof InjectedParametersDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                InjectedParametersDetailDescriptor detailDescriptor = new InjectedParametersDetailDescriptor( (InjectedParametersDescriptor) visited );

                // Invoked for constructor and injected method
                if( currConstructorDescriptor != null )
                {
                    currConstructorDescriptor.setInjectedParameter( detailDescriptor );
                }
                else if( currInjectedMethodDescriptor != null )
                {
                    currInjectedMethodDescriptor.setInjectedParameter( detailDescriptor );
                }
                else
                {
                    throw new IllegalStateException( "InjectedParametersDescriptor is only valid for "
                                                     + "constructor and injector method descriptor." );
                }
            }
            else if( visited instanceof InjectedMethodDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                // Invoked for mixin and object
                currInjectedMethodDescriptor = new InjectedMethodDetailDescriptor( (InjectedMethodDescriptor) visited );
                currConstructorDescriptor = null;

                // Invoked for mixin and object
                if( currActivatorDescriptor != null )
                {
                    currActivatorDescriptor.addInjectedMethod( currInjectedMethodDescriptor );
                }
                else if( currMixinDescriptor != null )
                {
                    currMixinDescriptor.addInjectedMethod( currInjectedMethodDescriptor );
                }
                else if( currObjectDescriptor != null )
                {
                    currObjectDescriptor.addInjectedMethod( currInjectedMethodDescriptor );
                }
                else if( currMethodConcernDescriptor != null )
                {
                    currMethodConcernDescriptor.addInjectedMethod( currInjectedMethodDescriptor );
                }
                else if( currMethodSideEffectDescriptor != null )
                {
                    currMethodSideEffectDescriptor.addInjectedMethod( currInjectedMethodDescriptor );
                }
                else
                {
                    throw new IllegalStateException( "InjectedMethodDescriptor is only valid for "
                                                     + "mixin, object, concern and side-effect." );
                }
            }

            return true;
        }

        @Override
        public boolean visitLeave( Object visited )
            throws RuntimeException
        {
            return true;
        }

        @Override
        public boolean visit( Object visited )
            throws RuntimeException
        {
            if( visited instanceof ConstraintDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                MethodConstraintDetailDescriptor detailDescriptor = new MethodConstraintDetailDescriptor( (ConstraintDescriptor) visited );
                currMethodConstraintsDescriptor.addConstraint( detailDescriptor );
            }
            else if( visited instanceof InjectedFieldDescriptor )
            {
                if( currCompositeDescriptor == null )
                {
                    // Service via CompositeDescriptor in progress )
                    return false;
                }
                InjectedFieldDetailDescriptor detailDescriptor = new InjectedFieldDetailDescriptor( (InjectedFieldDescriptor) visited );

                // Invoked for mixin and object
                if( currActivatorDescriptor != null )
                {
                    currActivatorDescriptor.addInjectedField( detailDescriptor );
                }
                else if( currMixinDescriptor != null )
                {
                    currMixinDescriptor.addInjectedField( detailDescriptor );
                }
                else if( currObjectDescriptor != null )
                {
                    currObjectDescriptor.addInjectedField( detailDescriptor );
                }
                else if( currMethodConcernDescriptor != null )
                {
                    currMethodConcernDescriptor.addInjectedField( detailDescriptor );
                }
                else if( currMethodSideEffectDescriptor != null )
                {
                    currMethodSideEffectDescriptor.addInjectedField( detailDescriptor );
                }
                else
                {
                    throw new IllegalStateException( "InjectedFieldDescriptor is only valid for "
                                                     + "mixin, object, concern and side-effect." );
                }
            }

            return true;
        }

        private LayerDetailDescriptor getLayerDetailDescriptor( LayerDescriptor aDescriptor )
        {
            LayerDetailDescriptor detailDescriptor = layerDescToDetail.get( aDescriptor );
            if( detailDescriptor == null )
            {
                detailDescriptor = new LayerDetailDescriptor( aDescriptor );
                layerDescToDetail.put( aDescriptor, detailDescriptor );
            }

            return detailDescriptor;
        }

        private void resetInjectableRelatedVariables()
        {
            currMixinDescriptor = null;
            currObjectDescriptor = null;
            currMethodConcernDescriptor = null;
            currMethodSideEffectDescriptor = null;
        }
    }

}
