/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.myfaces.extensions.validator.core;

import org.apache.myfaces.extensions.validator.core.initializer.component.ComponentInitializer;
import org.apache.myfaces.extensions.validator.core.interceptor.MetaDataExtractionInterceptor;
import org.apache.myfaces.extensions.validator.core.interceptor.PropertyValidationInterceptor;
import org.apache.myfaces.extensions.validator.core.interceptor.ValidationExceptionInterceptor;
import org.apache.myfaces.extensions.validator.internal.UsageCategory;
import org.apache.myfaces.extensions.validator.internal.UsageInformation;
import org.apache.myfaces.extensions.validator.util.ClassUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * @author Gerhard Petracek
 * @since x.x.3
 */
@UsageInformation(UsageCategory.INTERNAL)
class ExtValContextInvocationOrderAwareInternals
{
    private final Logger logger = Logger.getLogger(getClass().getName());

    private List<MetaDataExtractionInterceptor> metaDataExtractionInterceptors = null;
    private Map<Class, List<MetaDataExtractionInterceptor>> moduleSpecificMetaDataExtractionInterceptors = null;
    private List<ValidationExceptionInterceptor> validationExceptionInterceptors = null;
    private List<PropertyValidationInterceptor> propertyValidationInterceptors = null;
    private Map<Class, List<PropertyValidationInterceptor>> moduleSpecificPropertyValidationInterceptors = null;
    private List<ComponentInitializer> componentInitializers = null;

    private ExtValContextInternals contextHelper;

    ExtValContextInvocationOrderAwareInternals(ExtValContextInternals contextHelper)
    {
        this.contextHelper = contextHelper;
    }

    /*
     * ComponentInitializers
     */
    void addComponentInitializer(ComponentInitializer componentInitializer)
    {
        this.componentInitializers.add(componentInitializer);
        sortComponentInitializers();
    }

    List<ComponentInitializer> getComponentInitializers()
    {
        return this.contextHelper.isComponentInitializationActivated() ?
                this.componentInitializers : new CopyOnWriteArrayList<ComponentInitializer>();
    }

    /*
     * ValidationExceptionInterceptors
     */
    void addValidationExceptionInterceptor(ValidationExceptionInterceptor validationExceptionInterceptor)
    {
        this.validationExceptionInterceptors.add(validationExceptionInterceptor);
        sortValidationExceptionInterceptors();
    }

    List<ValidationExceptionInterceptor> getValidationExceptionInterceptors()
    {
        return this.validationExceptionInterceptors;
    }

    /*
     * PropertyValidationInterceptors
     */
    void addPropertyValidationInterceptor(PropertyValidationInterceptor propertyValidationInterceptor)
    {
        if (propertyValidationInterceptor instanceof ValidationModuleAware)
        {
            addPropertyValidationInterceptorForModules(propertyValidationInterceptor);
            sortModuleSpecificPropertyValidationInterceptors();
        }
        else
        {
            addPropertyValidationInterceptorForModule(null, propertyValidationInterceptor);
            sortPropertyValidationInterceptors();
        }
    }

    List<PropertyValidationInterceptor> getPropertyValidationInterceptors()
    {
        return this.propertyValidationInterceptors;
    }

    List<PropertyValidationInterceptor> getPropertyValidationInterceptorsFor(Class moduleKey)
    {
        List<PropertyValidationInterceptor> result = new ArrayList<PropertyValidationInterceptor>();

        result.addAll(getPropertyValidationInterceptors());

        if (moduleKey != null && this.moduleSpecificPropertyValidationInterceptors.containsKey(moduleKey))
        {
            result.addAll(this.moduleSpecificPropertyValidationInterceptors.get(moduleKey));
        }
        return sortPropertyValidationInterceptorList(result);
    }

    private void addPropertyValidationInterceptorForModules(PropertyValidationInterceptor propertyValidationInterceptor)
    {
        Class moduleKey;
        for (String currentModuleKey : ((ValidationModuleAware) propertyValidationInterceptor).getModuleKeys())
        {
            moduleKey = ClassUtils.tryToLoadClassForName(currentModuleKey);

            if (moduleKey == null)
            {
                continue;
            }

            addPropertyValidationInterceptorForModule(moduleKey, propertyValidationInterceptor);
        }
    }

    private void addPropertyValidationInterceptorForModule(
            Class moduleKey, PropertyValidationInterceptor propertyValidationInterceptor)
    {
        if (moduleKey == null)
        {
            this.propertyValidationInterceptors.add(propertyValidationInterceptor);

            logger.finest(propertyValidationInterceptor.getClass().getName() + " added as global interceptor");
        }
        else
        {
            List<PropertyValidationInterceptor> propertyValidationInterceptorList;
            if (this.moduleSpecificPropertyValidationInterceptors.containsKey(moduleKey))
            {
                propertyValidationInterceptorList = this.moduleSpecificPropertyValidationInterceptors.get(moduleKey);
            }
            else
            {
                propertyValidationInterceptorList = new CopyOnWriteArrayList<PropertyValidationInterceptor>();
                this.moduleSpecificPropertyValidationInterceptors.put(moduleKey, propertyValidationInterceptorList);
            }
            propertyValidationInterceptorList.add(propertyValidationInterceptor);

            logger.finest(propertyValidationInterceptor.getClass().getName() + " added for " + moduleKey.getName());
        }
    }

    /*
     * MetaDataExtractionInterceptors
     */
    void addMetaDataExtractionInterceptor(MetaDataExtractionInterceptor metaDataExtractionInterceptor)
    {
        if(metaDataExtractionInterceptor instanceof ValidationModuleAware)
        {
            addMetaDataExtractionInterceptorForModules(metaDataExtractionInterceptor);
            sortModuleSpecificMetaDataExtractionInterceptors();
        }
        else
        {
            addMetaDataExtractionInterceptorForModule(null, metaDataExtractionInterceptor);
            sortMetaDataExtractionInterceptors();
        }
    }

    private void addMetaDataExtractionInterceptorForModules(MetaDataExtractionInterceptor metaDataExtractionInterceptor)
    {
        Class moduleKey;
        for (String currentModuleKey : ((ValidationModuleAware) metaDataExtractionInterceptor).getModuleKeys())
        {
            moduleKey = ClassUtils.tryToLoadClassForName(currentModuleKey);

            if (moduleKey == null)
            {
                continue;
            }

            addMetaDataExtractionInterceptorForModule(moduleKey, metaDataExtractionInterceptor);
        }
    }

    private void addMetaDataExtractionInterceptorForModule(
            Class moduleKey, MetaDataExtractionInterceptor metaDataExtractionInterceptor)
    {
        if (moduleKey == null)
        {
            this.metaDataExtractionInterceptors.add(metaDataExtractionInterceptor);

            logger.finest(metaDataExtractionInterceptor.getClass().getName() + " added as global interceptor");
        }
        else
        {
            List<MetaDataExtractionInterceptor> metaDataExtractionInterceptorList;
            if (this.moduleSpecificMetaDataExtractionInterceptors.containsKey(moduleKey))
            {
                metaDataExtractionInterceptorList = this.moduleSpecificMetaDataExtractionInterceptors.get(moduleKey);
            }
            else
            {
                metaDataExtractionInterceptorList = new CopyOnWriteArrayList<MetaDataExtractionInterceptor>();
                this.moduleSpecificMetaDataExtractionInterceptors.put(moduleKey, metaDataExtractionInterceptorList);
            }
            metaDataExtractionInterceptorList.add(metaDataExtractionInterceptor);

            logger.finest(metaDataExtractionInterceptor.getClass().getName() + " added for " + moduleKey.getName());
        }
    }

    List<MetaDataExtractionInterceptor> getMetaDataExtractionInterceptors()
    {
        return this.metaDataExtractionInterceptors;
    }

    List<MetaDataExtractionInterceptor> getMetaDataExtractionInterceptorsWith(Map<String, Object> properties)
    {
        List<MetaDataExtractionInterceptor> result = new ArrayList<MetaDataExtractionInterceptor>();

        result.addAll(getMetaDataExtractionInterceptors());

        Class moduleKey = tryToResolveModuleKey(properties);
        if(moduleKey != null && this.moduleSpecificMetaDataExtractionInterceptors.containsKey(moduleKey))
        {
            result.addAll(this.moduleSpecificMetaDataExtractionInterceptors.get(moduleKey));
        }

        return sortMetaDataExtractionInterceptorList(result);
    }

    private Class tryToResolveModuleKey(Map<String, Object> properties)
    {
        Class moduleKey = null;
        if(properties != null && properties.containsKey(ValidationModuleKey.class.getName()))
        {
            Object foundValue = properties.get(ValidationModuleKey.class.getName());
            if(foundValue instanceof Class)
            {
                moduleKey = (Class)foundValue;
            }
        }
        return moduleKey;
    }

    /*
     * init
     */

    void lazyInitValidationExceptionInterceptors()
    {
        if (validationExceptionInterceptors != null)
        {
            return;
        }

        validationExceptionInterceptors = new CopyOnWriteArrayList<ValidationExceptionInterceptor>();
        List<String> validationExceptionInterceptorClassNames = new ArrayList<String>();

        validationExceptionInterceptorClassNames
                .add(ExtValCoreConfiguration.get().customValidationExceptionInterceptorClassName());
        validationExceptionInterceptorClassNames
                .add(this.contextHelper.getInformationProviderBean().get(
                        CustomInformation.VALIDATION_EXCEPTION_INTERCEPTOR));

        ValidationExceptionInterceptor validationExceptionInterceptor;
        for (String validationExceptionInterceptorName : validationExceptionInterceptorClassNames)
        {
            validationExceptionInterceptor =
                    (ValidationExceptionInterceptor)
                            ClassUtils.tryToInstantiateClassForName(validationExceptionInterceptorName);

            if (validationExceptionInterceptor != null)
            {
                validationExceptionInterceptors.add(validationExceptionInterceptor);

                logger.finest(validationExceptionInterceptor.getClass().getName() + " added");
            }
        }
    }

    void lazyInitMetaDataExtractionInterceptors()
    {
        if (metaDataExtractionInterceptors != null)
        {
            return;
        }

        metaDataExtractionInterceptors =
                new CopyOnWriteArrayList<MetaDataExtractionInterceptor>();
        moduleSpecificMetaDataExtractionInterceptors =
                new ConcurrentHashMap<Class, List<MetaDataExtractionInterceptor>>();

        List<String> metaDataExtractionInterceptorClassNames = new ArrayList<String>();

        metaDataExtractionInterceptorClassNames
                .add(ExtValCoreConfiguration.get().customMetaDataExtractionInterceptorClassName());
        metaDataExtractionInterceptorClassNames
                .add(this.contextHelper.getInformationProviderBean().get(
                        CustomInformation.META_DATA_EXTRACTION_INTERCEPTOR));

        MetaDataExtractionInterceptor metaDataExtractionInterceptor;
        for (String validationExceptionInterceptorName : metaDataExtractionInterceptorClassNames)
        {
            metaDataExtractionInterceptor =
                    (MetaDataExtractionInterceptor)
                            ClassUtils.tryToInstantiateClassForName(validationExceptionInterceptorName);

            if (metaDataExtractionInterceptor != null)
            {
                addMetaDataExtractionInterceptor(metaDataExtractionInterceptor);
            }
        }
    }

    void lazyInitComponentInitializers()
    {
        if (componentInitializers != null)
        {
            return;
        }

        componentInitializers = new CopyOnWriteArrayList<ComponentInitializer>();
        List<String> componentInitializerClassNames = new ArrayList<String>();
        componentInitializerClassNames
                .add(ExtValCoreConfiguration.get().customComponentInitializerClassName());
        componentInitializerClassNames
                .add(this.contextHelper.getInformationProviderBean().get(CustomInformation.COMPONENT_INITIALIZER));

        ComponentInitializer componentInitializer;
        for (String componentInitializerName : componentInitializerClassNames)
        {
            componentInitializer =
                    (ComponentInitializer) ClassUtils.tryToInstantiateClassForName(componentInitializerName);

            if (componentInitializer != null)
            {
                componentInitializers.add(componentInitializer);

                logger.finest(componentInitializer.getClass().getName() + " added");
            }
        }
    }

    void lazyInitPropertyValidationInterceptors()
    {
        if (propertyValidationInterceptors != null)
        {
            return;
        }

        propertyValidationInterceptors =
                new CopyOnWriteArrayList<PropertyValidationInterceptor>();
        moduleSpecificPropertyValidationInterceptors =
                new ConcurrentHashMap<Class, List<PropertyValidationInterceptor>>();

        List<String> validationInterceptorClassNames = new ArrayList<String>();

        validationInterceptorClassNames
                .add(ExtValCoreConfiguration.get().customPropertyValidationInterceptorClassName());
        validationInterceptorClassNames
                .add(this.contextHelper.getInformationProviderBean().get(
                        CustomInformation.PROPERTY_VALIDATION_INTERCEPTOR));

        PropertyValidationInterceptor propertyValidationInterceptor;
        for (String validationInterceptorName : validationInterceptorClassNames)
        {
            propertyValidationInterceptor =
                    (PropertyValidationInterceptor)
                            ClassUtils.tryToInstantiateClassForName(validationInterceptorName);

            if (propertyValidationInterceptor != null)
            {
                if (propertyValidationInterceptor instanceof ValidationModuleAware)
                {
                    addPropertyValidationInterceptorForModules(propertyValidationInterceptor);
                }
                else
                {
                    addPropertyValidationInterceptorForModule(null, propertyValidationInterceptor);
                }
            }
        }
    }

    /*
     * sort
     */
    private void sortComponentInitializers()
    {
        List<ComponentInitializer> componentInitializersToSort =
                new ArrayList<ComponentInitializer>(this.componentInitializers);

        Collections.sort(componentInitializersToSort, new InvocationOrderComparator<ComponentInitializer>());

        this.componentInitializers.clear();
        this.componentInitializers.addAll(componentInitializersToSort);
    }

    private void sortPropertyValidationInterceptors()
    {
        List<PropertyValidationInterceptor> propertyValidationInterceptorsToSort =
                new ArrayList<PropertyValidationInterceptor>(this.propertyValidationInterceptors);
        Collections.sort(propertyValidationInterceptorsToSort,
                new InvocationOrderComparator<PropertyValidationInterceptor>());

        this.propertyValidationInterceptors.clear();
        this.propertyValidationInterceptors.addAll(propertyValidationInterceptorsToSort);
    }

    //sort all - it isn't a huge overhead since it's just done during the init-phase
    private void sortModuleSpecificPropertyValidationInterceptors()
    {
        for(List<PropertyValidationInterceptor> propertyValidationInterceptorList :
                this.moduleSpecificPropertyValidationInterceptors.values())
        {
            sortPropertyValidationInterceptorList(propertyValidationInterceptorList);
        }
    }

    private List<PropertyValidationInterceptor> sortPropertyValidationInterceptorList(
            List<PropertyValidationInterceptor> propertyValidationInterceptorList)
    {
        List<PropertyValidationInterceptor> propertyValidationInterceptorListToSort =
                new ArrayList<PropertyValidationInterceptor>(propertyValidationInterceptorList);

        Collections.sort(propertyValidationInterceptorListToSort,
                    new InvocationOrderComparator<PropertyValidationInterceptor>());

        propertyValidationInterceptorList.clear();
        propertyValidationInterceptorList.addAll(propertyValidationInterceptorListToSort);
        return propertyValidationInterceptorList;
    }

    private void sortValidationExceptionInterceptors()
    {
        List<ValidationExceptionInterceptor> validationExceptionInterceptorsToSort =
                new ArrayList<ValidationExceptionInterceptor>(this.validationExceptionInterceptors);

        Collections.sort(validationExceptionInterceptorsToSort,
                new InvocationOrderComparator<ValidationExceptionInterceptor>());

        this.validationExceptionInterceptors.clear();
        this.validationExceptionInterceptors.addAll(validationExceptionInterceptorsToSort);
    }

    private void sortMetaDataExtractionInterceptors()
    {
        List<MetaDataExtractionInterceptor> metaDataExtractionInterceptorsToSort =
                new ArrayList<MetaDataExtractionInterceptor>(this.metaDataExtractionInterceptors);

        Collections.sort(metaDataExtractionInterceptorsToSort,
                new InvocationOrderComparator<MetaDataExtractionInterceptor>());

        this.metaDataExtractionInterceptors.clear();
        this.metaDataExtractionInterceptors.addAll(metaDataExtractionInterceptorsToSort);
    }

    //sort all - it isn't a huge overhead since it's just done during the init-phase
    private void sortModuleSpecificMetaDataExtractionInterceptors()
    {
        for(List<MetaDataExtractionInterceptor> metaDataExtractionInterceptorList :
                this.moduleSpecificMetaDataExtractionInterceptors.values())
        {
            sortMetaDataExtractionInterceptorList(metaDataExtractionInterceptorList);
        }
    }

    private List<MetaDataExtractionInterceptor> sortMetaDataExtractionInterceptorList(
            List<MetaDataExtractionInterceptor> metaDataExtractionInterceptorList)
    {
        List<MetaDataExtractionInterceptor> metaDataExtractionInterceptorListToSort =
                new ArrayList<MetaDataExtractionInterceptor>(metaDataExtractionInterceptorList);

        Collections.sort(metaDataExtractionInterceptorListToSort,
                    new InvocationOrderComparator<MetaDataExtractionInterceptor>());

        metaDataExtractionInterceptorList.clear();
        metaDataExtractionInterceptorList.addAll(metaDataExtractionInterceptorListToSort);
        return metaDataExtractionInterceptorList;
    }
}
