/*
 * 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.clerezza.platform.typerendering;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.ws.rs.core.MediaType;
import org.apache.clerezza.commons.rdf.IRI;
import org.apache.clerezza.rdf.utils.GraphNode;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.scr.annotations.Services;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
@Services({
    @Service(RenderletManager.class)
})
@Reference(name = "renderlet",
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
policy = ReferencePolicy.DYNAMIC,
referenceInterface = Renderlet.class)
@Deprecated
public class RenderletManagerImpl implements RenderletManager {
    private Collection<RegistrationRequest> pendingRequests
            = new HashSet<RegistrationRequest>();



    private static class RegistrationRequest {

        String renderletServiceName;
        IRI renderingSpecification;
        IRI rdfType;
        String mode;
        MediaType mediaType;

        public RegistrationRequest(String renderletServiceName, IRI renderingSpecification, IRI rdfType, String mode, MediaType mediaType) {
            this.renderletServiceName = renderletServiceName;
            this.renderingSpecification = renderingSpecification;
            this.rdfType = rdfType;
            this.mode = mode;
            this.mediaType = mediaType;
        }
    }
    private Logger logger = LoggerFactory.getLogger(RenderletManagerImpl.class);
    private ComponentContext componentContext;
    private final Set<ServiceReference> renderletRefStore =
            Collections.synchronizedSet(new HashSet<ServiceReference>());
    private final Set<ServiceRegistration> registeredTypeRenderlets =
            Collections.synchronizedSet(new HashSet<ServiceRegistration>());
    private ReentrantReadWriteLock configLock = new ReentrantReadWriteLock();
    /**
     * maps from service-id to the renderlet
     */
    private Map<String, Renderlet> renderletMap = new HashMap<String, Renderlet>();
    private BundleContext bundleContext;

    /**
     * Registeres a renderlet.
     * 
     * For the same rdfType, mediaType and Mode at motst one built-in and one 
     * non-built-in renderlet can be registered. An attempt to register a second 
     * renderlet results in the unregistration of the previously registered one 
     * 
     * @param renderingSpecification the argument that is passed to the
     *     renderlet.
     * @param rdfType defines the RDF-type to be rendered with specified renderlet
     *     and renderingSpecification and mode.
     * @param mode defines the mode in which the renderlet and rendering
     *        specification has to be used.
     *        mode may be null, that indicates that it is only used when no mode is
     *        required.
     * @param mediaType The media type of the rendered
     * @param builtIn ignored
     */
    public void registerRenderlet(String renderletServiceName,
            final IRI renderingSpecification,
            final IRI rdfType,
            final String mode,
            final MediaType mediaType, boolean builtIn) {
        RegistrationRequest registrationRequest = new RegistrationRequest(renderletServiceName,
                renderingSpecification,
                rdfType,
                mode,
                mediaType);
        if (!attemptRegistration(registrationRequest)) {
            logger.info("no renderlet service registered for "
                    + renderletServiceName + ", will retry later.");
            pendingRequests.add(registrationRequest);
        }
    }

    protected void bindRenderlet(ServiceReference renderletRef) {
        logger.info("Bind renderlet of bundle {}", renderletRef.getBundle().getSymbolicName());
        if (componentContext != null) {
            registerRenderletService(renderletRef);
        } else {
            renderletRefStore.add(renderletRef);
        }
    }

    private void registerRenderletService(ServiceReference renderletRef) {
        String servicePid = (String) renderletRef.getProperty(Constants.SERVICE_PID);
        Renderlet renderlet = (Renderlet) componentContext.locateService("renderlet", renderletRef);
        registerRenderletService(servicePid, renderlet);
    }

    protected void registerRenderletService(String servicePid, Renderlet renderlet) {
        configLock.writeLock().lock();
        try {
            if (renderletMap.get(servicePid) == null) {
                renderletMap.put(servicePid, renderlet);
            }
        } finally {
            configLock.writeLock().unlock();
        }
        handlePendingRegistrations();
    }

    protected void unbindRenderlet(ServiceReference renderletRef) {
        Lock l = configLock.writeLock();
        l.lock();
        try {
            if (!renderletRefStore.remove(renderletRef)) {
                String servicePid = (String) renderletRef.getProperty(Constants.SERVICE_PID);
                Renderlet renderlet = (Renderlet) componentContext.locateService("renderlet", renderletRef);
                unregisterRenderletService(servicePid, renderlet);
            }
        } finally {
            l.unlock();
        }
    }

    protected void unregisterRenderletService(String servicePid, Renderlet renderlet) {
        logger.debug("unregistering {} with pid {}", renderlet, servicePid);
        configLock.writeLock().lock();
        try {
            renderletMap.remove(servicePid);
        } finally {
            configLock.writeLock().unlock();
        }
    }

    private void registerRenderletsFromStore() {
        synchronized (renderletRefStore) {
            for (ServiceReference renderletRef : renderletRefStore) {
                registerRenderletService(renderletRef);
            }
        }
        renderletRefStore.clear();
    }

    /**
     * The activate method is called when SCR activates the component
     * configuration
     *
     * @param componentContext
     */
    protected void activate(ComponentContext componentContext) {
        bundleContext = componentContext.getBundleContext();
        this.componentContext = componentContext;
        registerRenderletsFromStore();
    }

    /**
     * The deactivate method is called when SCR deactivates the component
     * configuration
     *
     * @param componentContext
     */
    protected void deactivate(ComponentContext componentContext) {
        for (ServiceRegistration registration : registeredTypeRenderlets) {
            registration.unregister();
        }
        registeredTypeRenderlets.clear();
        bundleContext = null;
    }

    private void handlePendingRegistrations() {
        Lock l = configLock.readLock();
        l.lock();
        try {
            Iterator<RegistrationRequest> iterator = pendingRequests.iterator();
            while (iterator.hasNext()) {
                RegistrationRequest pending = iterator.next();
                if (attemptRegistration(pending)) {
                    iterator.remove();
                }
            }
        } finally {
            l.unlock();
        }
    }

    private boolean attemptRegistration(RegistrationRequest registrationRequest) {
        final String renderletServiceName = registrationRequest.renderletServiceName;
        IRI renderingSpecification = registrationRequest.renderingSpecification;
        final IRI rdfType = registrationRequest.rdfType;
        final String modePattern = registrationRequest.mode;
        final MediaType mediaType = registrationRequest.mediaType;

        final Renderlet renderlet = renderletMap.get(renderletServiceName);
        if (renderlet == null) {
            return false;
        }
        final URI renderingSpecificationUri;
        try {
            renderingSpecificationUri = renderingSpecification != null
                    ? new URI(renderingSpecification.getUnicodeString())
                    : null;
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
        TypeRenderlet typeRenderlet = new TypeRenderlet() {

            @Override
            public IRI getRdfType() {
                return rdfType;
            }

            @Override
            public String getModePattern() {
                return modePattern;
            }

            @Override
            public MediaType getMediaType() {
                return mediaType;
            }

            @Override
            public void render(GraphNode node, GraphNode context,
                    Map<String, Object> sharedRenderingValues,
                    CallbackRenderer callbackRenderer,
                    RequestProperties typeRenderletRequestProperties,
                    OutputStream os) throws IOException {
                Renderlet.RequestProperties renderletRequestProperties =
                        new Renderlet.RequestProperties(typeRenderletRequestProperties.getUriInfo(),
                        typeRenderletRequestProperties.getRequestHeaders(),
                        typeRenderletRequestProperties.getResponseHeaders(), bundleContext);
                renderlet.render(node, context, sharedRenderingValues,
                        callbackRenderer, renderingSpecificationUri, 
                        typeRenderletRequestProperties.getMode(),
                        typeRenderletRequestProperties.getMediaType(),
                        renderletRequestProperties, os);
            }

            @Override
            public String toString() {
                return "RenderletManager managed for: "+renderletServiceName+" with "+renderingSpecificationUri;
            }


        };

        ServiceRegistration oldServiceRegistration =
                getAlreadyRegisteredServiceReg(rdfType, mediaType, modePattern);
        if(oldServiceRegistration != null) {
            oldServiceRegistration.unregister();
            registeredTypeRenderlets.remove(oldServiceRegistration);
        }

        ServiceRegistration registration = bundleContext.registerService(TypeRenderlet.class.getName(), typeRenderlet,
                new Hashtable());
        registeredTypeRenderlets.add(registration);
        return true;
    }

    private ServiceRegistration getAlreadyRegisteredServiceReg(IRI rdfType, 
            MediaType mediaType, String modePattern) {
        
        for (ServiceRegistration serviceRegistration : registeredTypeRenderlets) {
            TypeRenderlet registeredRenderlet = (TypeRenderlet)
                    bundleContext.getService(serviceRegistration.getReference());
            if (registeredRenderlet != null
                    && registeredRenderlet.getRdfType().equals(rdfType)
                    && registeredRenderlet.getMediaType().equals(mediaType)) {

                if(modePattern == null && registeredRenderlet.getModePattern() == null) {
                    return serviceRegistration;
                } else if(modePattern != null && registeredRenderlet.getModePattern() != null) {
                    if(registeredRenderlet.getModePattern().equals(modePattern)) {
                        return serviceRegistration;
                    }
                }
            }
        }
        return null;
    }
}
