/*
 * 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.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Dictionary;

import org.apache.felix.dm.BundleDependency;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentDependencyDeclaration;
import org.apache.felix.dm.context.AbstractDependency;
import org.apache.felix.dm.context.DependencyContext;
import org.apache.felix.dm.context.Event;
import org.apache.felix.dm.context.EventType;
import org.apache.felix.dm.tracker.BundleTracker;
import org.apache.felix.dm.tracker.BundleTrackerCustomizer;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;

/**
 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 */
public class BundleDependencyImpl extends AbstractDependency<BundleDependency> implements BundleDependency, BundleTrackerCustomizer, ComponentDependencyDeclaration {
    private BundleTracker m_tracker;
    private volatile int m_stateMask = Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE;
    private Bundle m_bundleInstance;
    private volatile Filter m_filter;
    private volatile long m_bundleId = -1;
    private Object m_nullObject;
    private volatile boolean m_propagate;
    private volatile Object m_propagateCallbackInstance;
    private volatile String m_propagateCallbackMethod;

    public BundleDependencyImpl() {
    }
    
    public BundleDependencyImpl(BundleDependencyImpl prototype) {
        super(prototype);
        m_stateMask = prototype.m_stateMask;
        m_nullObject = prototype.m_nullObject;
        m_bundleInstance = prototype.m_bundleInstance;
        m_filter = prototype.m_filter;
        m_bundleId = prototype.m_bundleId;
        m_propagate = prototype.m_propagate;
        m_propagateCallbackInstance = prototype.m_propagateCallbackInstance;
        m_propagateCallbackMethod = prototype.m_propagateCallbackMethod;       
    }
    
    @Override
    public DependencyContext createCopy() {
        return new BundleDependencyImpl(this);
    }
    
    @Override
    public void start() {
        m_tracker = new BundleTracker(m_component.getBundleContext(), m_stateMask, this);
        m_tracker.open();
        super.start();
    }

    @Override
    public void stop() {
        m_tracker.close();
        m_tracker = null;
        super.stop();
    }
    
    @Override
    public String getName() {
        StringBuilder sb = new StringBuilder();
        getSimpleName(sb);
        if (m_filter != null) {
            sb.append(" ");
            sb.append(m_filter.toString());
        }
        if (m_bundleId != -1) {
            sb.append("{bundle.id=" + m_bundleId + "}");
        }
        return sb.toString();
    }

    @Override
    public String getSimpleName() {
        // Return the state mask, but don't include the filter or bundle id.
        StringBuilder sb = new StringBuilder();
        if ((m_stateMask & Bundle.ACTIVE) != 0) {
            sb.append("active");
        }
        if ((m_stateMask & Bundle.INSTALLED) != 0) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            sb.append("installed");
        }
        if ((m_stateMask & Bundle.RESOLVED) != 0) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            sb.append("resolved");
        }
        return sb.toString();
    }
    
    @Override
    public String getFilter() {
        if (m_filter != null || m_bundleId != -1) {
            StringBuilder sb = new StringBuilder();
            if (m_filter != null) {
                sb.append(m_filter.toString());
            }
            if (m_bundleId != -1) {
                sb.append("{bundle.id=" + m_bundleId + "}");
            }
            return sb.toString();
        }
        return null;
    }

    @Override
    public String getType() {
        return "bundle";
    }

    public Object addingBundle(Bundle bundle, BundleEvent event) {
        // if we don't like a bundle, we could reject it here by returning null
        long bundleId = bundle.getBundleId();
        if (m_bundleId >= 0 && m_bundleId != bundleId) {
            return null;
        }
        Filter filter = m_filter;
        if (filter != null) {
            Dictionary<String,?> headers = bundle.getHeaders();
            if (!m_filter.match(headers)) {
                return null;
            }
        }
        return bundle;
    }
    
    public void addedBundle(Bundle bundle, BundleEvent event, Object object) {
        m_component.handleEvent(this, EventType.ADDED, new BundleEventImpl(bundle, event));
    }
        
    public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
        m_component.handleEvent(this, EventType.CHANGED, new BundleEventImpl(bundle, event));
    }

    public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
        m_component.handleEvent(this, EventType.REMOVED, new BundleEventImpl(bundle, event));
    }
    
    @Override
    public void invokeCallback(EventType type, Event ... e) {
        switch (type) {
        case ADDED:
            if (m_add != null) {
                invoke(m_add, e[0]);
            }
            break;
        case CHANGED:
            if (m_change != null) {
                invoke (m_change, e[0]);
            }
            break;
        case REMOVED:
            if (m_remove != null) {
                invoke (m_remove, e[0]);
            }
            break;
        default:
            break;
        }
    }
            
    private void invoke(String method, Event e) {
        BundleEventImpl be = (BundleEventImpl) e;
        m_component.invokeCallbackMethod(getInstances(), method,
            new Class[][] {{Bundle.class}, {Object.class}, {Component.class, Bundle.class}, {}},             
            new Object[][] {{be.getBundle()}, {be.getBundle()}, {m_component, be.getBundle()}, {}});
    }
    
    public BundleDependency setBundle(Bundle bundle) {
        m_bundleId = bundle.getBundleId();
        return this;
    }

    public BundleDependency setFilter(String filter) throws IllegalArgumentException {
        if (filter != null) {
            try {
                m_filter = FrameworkUtil.createFilter(filter);
            } 
            catch (InvalidSyntaxException e) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
        return this;
    }
    
    public BundleDependency setStateMask(int mask) {
        m_stateMask = mask;
        return this;
    }
    
    @Override
    public Class<?> getAutoConfigType() {
        return Bundle.class;
    }
        
    @SuppressWarnings("unchecked")
    @Override
    public Dictionary<String, Object> getProperties() {
        Event event = getService();
        if (event != null) {
            Bundle bundle = (Bundle) event.getEvent();
            if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
                try {
                    CallbackTypeDef callbackInfo = new CallbackTypeDef(Bundle.class, bundle);
                    return (Dictionary<String, Object>) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, callbackInfo.m_sigs, callbackInfo.m_args);
                }
                catch (InvocationTargetException e) {
                    m_component.getLogger().warn("Exception while invoking callback method", e.getCause());
                }
                catch (Throwable e) {
                    m_component.getLogger().warn("Exception while trying to invoke callback method", e);
                }
                throw new IllegalStateException("Could not invoke callback");
            }
            else {
                return ServiceUtil.toR6Dictionary(bundle.getHeaders());
            }
        }
        else {
            throw new IllegalStateException("cannot find bundle");
        }
    }
    
    @Override
    public Object getDefaultService(boolean nullObject) {
        Object service = null;
        if (isAutoConfig()) {
            // TODO does it make sense to add support for custom bundle impls?
            // service = getDefaultImplementation();
            if (service == null && nullObject) {
                service = getNullObject();
            }
        }
        return service;
    }

    private Bundle getNullObject() {
        if (m_nullObject == null) {
            try {
                m_nullObject = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Bundle.class }, new DefaultNullObject()); 
            }
            catch (Throwable e) {
                m_component.getLogger().err("Could not create null object for Bundle.", e);
            }
        }
        return (Bundle) m_nullObject;
    }
    
    private void getSimpleName(StringBuilder sb) {
        if ((m_stateMask & Bundle.ACTIVE) != 0) {
            sb.append("active");
        }
        if ((m_stateMask & Bundle.INSTALLED) != 0) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            sb.append("installed");
        }
        if ((m_stateMask & Bundle.RESOLVED) != 0) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            sb.append("resolved");
        }
    }
}

