/*
 * 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.felix.dm.runtime;

import org.apache.felix.dm.BundleDependency;
import org.apache.felix.dm.ConfigurationDependency;
import org.apache.felix.dm.Dependency;
import org.apache.felix.dm.DependencyManager;
import org.apache.felix.dm.ResourceDependency;
import org.apache.felix.dm.ServiceDependency;
import org.osgi.framework.Bundle;

/**
 * Class used to build a concrete dependency from meta data.
 * 
 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 */
public class DependencyBuilder
{
    public enum DependencyType
    {
        ServiceDependency,
        TemporalServiceDependency,
        ConfigurationDependency,
        BundleDependency,
        ResourceDependency
    }

    private MetaData m_metaData;

    public DependencyBuilder(MetaData dependencyMetaData)
    {
        m_metaData = dependencyMetaData;
    }

    public Dependency build(Bundle b, DependencyManager dm)
        throws Exception
    {
        Dependency dp = null;
        DependencyType type;

        try
        {
            type = DependencyType.valueOf(m_metaData.getString(Params.type));
        }
        catch (IllegalArgumentException e)
        {
            throw new IllegalArgumentException("no \"type\" parameter found from metaData: " + m_metaData);
        }

        switch (type)
        {
            case ServiceDependency:
                dp = createServiceDependency(b, dm);
                break;

            case ConfigurationDependency:
                dp = createConfigurationDependency(b, dm);
                break;

            case BundleDependency:
                dp = createBundleDependency(dm);
                break;

            case ResourceDependency:
                dp = createResourceDependency(dm);
                break;
            
            default:
                throw new IllegalArgumentException("Can't build service dependency: " + type);
        }
        return dp;
    }

    private Dependency createServiceDependency(Bundle b, DependencyManager dm)
        throws ClassNotFoundException
    {
        String service = m_metaData.getString(Params.service, null);
        Class<?> serviceClass = service != null ? b.loadClass(service) : null;
        String serviceFilter = m_metaData.getString(Params.filter, null);
        String defaultServiceImpl = m_metaData.getString(Params.defaultImpl, null);
        Class<?> defaultServiceImplClass =
            (defaultServiceImpl != null) ? b.loadClass(defaultServiceImpl) : null;
        String added = m_metaData.getString(Params.added, null);
        long timeout = m_metaData.getLong(Params.timeout, -1L);
        String changed = timeout != -1 ? null : m_metaData.getString(Params.changed, null);
        String removed = timeout != -1 ? null : m_metaData.getString(Params.removed, null);
        String swap = m_metaData.getString(Params.swap, null);
        String autoConfigField = m_metaData.getString(Params.autoConfig, null);
        boolean required = "true".equals(m_metaData.getString(Params.required, "true"));
        boolean propagate = "true".equals(m_metaData.getString(Params.propagate, "false"));
        boolean dereference = "true".equals(m_metaData.getString(Params.dereference, "true"));

        Dependency dp = createServiceDependency(dm, serviceClass,
            serviceFilter, defaultServiceImplClass, added, changed,
            removed, swap, autoConfigField, timeout, required, propagate, dereference);
        return dp;
    }

    private Dependency createServiceDependency(DependencyManager dm, Class<?> serviceClass, 
        String serviceFilter, Class<?> defaultServiceImplClass, String added,
        String changed, String removed, String swap, String autoConfigField, long timeout, boolean required,
        boolean propagate, boolean dereference)
    {
        ServiceDependency sd = timeout != -1 ? dm.createTemporalServiceDependency(timeout)
            : dm.createServiceDependency();
        sd.setService(serviceClass, serviceFilter);
        if (defaultServiceImplClass != null)
        {
            sd.setDefaultImplementation(defaultServiceImplClass);
        }
        sd.setCallbacks(added, changed, removed, swap);
        if (autoConfigField != null)
        {
            sd.setAutoConfig(autoConfigField);
        }
        if (timeout == -1)
        {
            sd.setRequired(required);
        }
        
        sd.setPropagate(propagate);
        sd.setDereference(dereference);
        return sd;
    }

    private Dependency createConfigurationDependency(Bundle b, DependencyManager dm) throws Exception
    {
        String configType = m_metaData.getString(Params.configType, null);
        String[] configTypes = m_metaData.getStrings(Params.configTypes, null);
        configTypes = configTypes == null ? ((configType != null) ? new String[] { configType } : null) : configTypes;
        String pid = m_metaData.getString(Params.pid);
        boolean propagate = "true".equals(m_metaData.getString(Params.propagate, "false"));
        String callback = m_metaData.getString(Params.updated, "updated");
        boolean required = "true".equals(m_metaData.getString(Params.required, "true"));
        return createConfigurationDependency(dm, b, pid, callback, configTypes, propagate, required);
    }

   private Dependency createConfigurationDependency(DependencyManager dm, Bundle b, String pid, String callback, String[] configTypes, boolean propagate, boolean required)
        throws ClassNotFoundException
    {
        if (pid == null)
        {
            throw new IllegalArgumentException(
                "pid attribute not provided in ConfigurationDependency declaration");
        }
        ConfigurationDependency cd = dm.createConfigurationDependency();
        cd.setPid(pid);
        cd.setCallback(callback);
        cd.setPropagate(propagate, true /* configuration will override default service properties */);
        cd.setRequired(required);
        
        if (configTypes != null) 
        {
            Class<?>[] configTypeClasses = new Class<?>[configTypes.length];
            for (int i = 0; i < configTypes.length; i ++)
            {
                configTypeClasses[i] = b.loadClass(configTypes[i]);
            }
            cd.setConfigType(configTypeClasses);            
        }        
        return cd;
    }

    /**
     * Creates a BundleDependency that we parsed from a component descriptor entry.
     * @param b
     * @param dm
     * @param parser
     * @return
     */
    private Dependency createBundleDependency(DependencyManager dm)
    {
        String added = m_metaData.getString(Params.added, null);
        String changed = m_metaData.getString(Params.changed, null);
        String removed = m_metaData.getString(Params.removed, null);
        boolean required = "true".equals(m_metaData.getString(Params.required, "true"));
        String filter = m_metaData.getString(Params.filter, null);
        int stateMask = m_metaData.getInt(Params.stateMask, -1);
        boolean propagate = "true".equals(m_metaData.getString(Params.propagate, "false"));

        Dependency dp = createBundleDependency(dm, added, changed, removed, required, propagate, filter,
            stateMask);
        return dp;
    }

    private Dependency createBundleDependency(DependencyManager dm, String added, String changed,
        String removed, boolean required, boolean propagate, String filter, int stateMask)
    {
        BundleDependency bd = dm.createBundleDependency();
        bd.setCallbacks(added, changed, removed);
        bd.setRequired(required);
        bd.setPropagate(propagate);
        if (filter != null)
        {
            bd.setFilter(filter);
        }
        if (stateMask != -1)
        {
            bd.setStateMask(stateMask);
        }
        return bd;
    }

    private Dependency createResourceDependency(DependencyManager dm)
    {
        String added = m_metaData.getString(Params.added, null);
        String changed = m_metaData.getString(Params.changed, null);
        String removed = m_metaData.getString(Params.removed, null);
        String filter = m_metaData.getString(Params.filter, null);
        boolean required = "true".equals(m_metaData.getString(Params.required, "true"));
        boolean propagate = "true".equals(m_metaData.getString(Params.propagate, "false"));
        String autoConfigField = m_metaData.getString(Params.autoConfig, null);

        Dependency dp = createResourceDependency(dm, added, changed, removed, required, filter, 
                                                 propagate, autoConfigField);
        return dp;
    }

    private Dependency createResourceDependency(DependencyManager dm, String added,
        String changed, String removed, boolean required, String filter, boolean propagate, 
        String autoConfigField)
    {
        ResourceDependency rd = dm.createResourceDependency();
        rd.setCallbacks(added, changed, removed);
        rd.setRequired(required);
        if (filter != null)
        {
            rd.setFilter(filter);
        }
        if (autoConfigField != null)
        {
            rd.setAutoConfig(autoConfigField);
        }
        rd.setPropagate(propagate);
        return rd;
    }
}
