/*
 * Copyright 2000-2001,2004 The Apache Software Foundation.
 * 
 * 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.apache.jetspeed.capabilities.impl;

import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jetspeed.capabilities.Capabilities;
import org.apache.jetspeed.capabilities.Capability;
import org.apache.jetspeed.capabilities.CapabilityMap;
import org.apache.jetspeed.capabilities.Client;
import org.apache.jetspeed.capabilities.MediaType;
import org.apache.jetspeed.capabilities.MimeType;
import org.apache.jetspeed.capabilities.UnableToBuildCapabilityMapException;
import org.apache.jetspeed.components.dao.InitablePersistenceBrokerDaoSupport;
import org.apache.ojb.broker.query.Criteria;
import org.apache.ojb.broker.query.QueryByCriteria;
import org.apache.ojb.broker.query.QueryFactory;

/**
 * Jetspeed Capabilities
 *
 * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
 * @author <a href="mailto:roger.ruttimann@earthlink.net">Roger Ruttimann</a>
 * @version $Id: JetspeedCapabilities.java,v 1.3 2005/01/04 15:52:02 weaver Exp $
 */
public class JetspeedCapabilities extends InitablePersistenceBrokerDaoSupport implements Capabilities 
{
    private String originalAlias;

    private static final Log log =
        LogFactory.getLog(JetspeedCapabilities.class);

    public static final String DEFAULT_AGENT = "Mozilla/4.0";

    public static final String AGENT_XML = "agentxml/1.0";

    // Cache for the capability maps
    Hashtable capabilityMapCache = new Hashtable();

    private Collection clients = null;

    private Class clientClass = ClientImpl.class;
    private Class capabilityClass = CapabilityImpl.class;
    private Class mimeTypeClass = MimeTypeImpl.class;
    private Class mediaTypeClass = MediaTypeImpl.class;

    public JetspeedCapabilities(String repositoryPath)
    {
        super(repositoryPath);
    }
    
    /**
     * Create a JetspeedProfiler with properties. Expected properties are:
     * 
     * 	   defaultRule   = the default profiling rule
     *     anonymousUser = the name of the anonymous user
     *     persistenceStoreName = The name of the persistence persistenceStore component to connect to  
     *     services.profiler.locator.impl = the pluggable Profile Locator impl
     *     services.profiler.principalRule.impl = the pluggable Principal Rule impl
     *     services.profiler.profilingRule.impl = the pluggable Profiling Rule impl
     *      
     * @param persistenceStore  The persistence persistenceStore 
     * @param properties  Properties for this component described above
     */
    public JetspeedCapabilities(String repositoryPath, Properties properties)
	{
        super(repositoryPath);
        initModelClasses(properties);
    }
    
    private void initModelClasses(Properties properties)
	{
        String modelName = "";
        try
        {
	        if ((modelName = properties.getProperty("client.impl")) != null)
	        {
	            clientClass = Class.forName(modelName);
	        }
	        if ((modelName = properties.getProperty("capability.impl")) != null)
	        {
	            capabilityClass = Class.forName(modelName);
	        }
	        if ((modelName = properties.getProperty("mimetype.impl")) != null)
	        {
	            mimeTypeClass = Class.forName(modelName);
	        }
	        if ((modelName = properties.getProperty("mediatype.impl")) != null)
	        {
	            mediaTypeClass = Class.forName(modelName);
	        }	        	        
	        
        }
        catch (ClassNotFoundException e)
        {
            log.error("Model class not found: " + modelName);
        }
    }
    

    /**
     * @param userAgent Agent from the request
     * @throws UnableToBuildCapabilityMapException
     * @see org.apache.jetspeed.services.capability.CapabilityService#getCapabilityMap(java.lang.String)
     */
    public CapabilityMap getCapabilityMap(String userAgent) throws UnableToBuildCapabilityMapException
    {        
        CapabilityMap map = null;
        boolean bClientFound = false;

        if (userAgent == null)
        {
            userAgent = DEFAULT_AGENT;
        }

        // Check the cache if we have already a capability map for
        // the given Agent
        map = (CapabilityMap) capabilityMapCache.get(userAgent);

        if (map != null)
        {
            // Entry found
            return map;
        }

        while (!bClientFound)
        {
            Client entry = findClient(userAgent);

            if (entry == null)
            {
                if (userAgent.equals(DEFAULT_AGENT))
                {
                    log.error(
                        "CapabilityMap: Default agent not found in Client Registry !");

                    // Stop searching -- event the default userAgent can't be found
                    bClientFound = true;
                } else
                {
                    // Retry with the default Agent
                    if (log.isDebugEnabled())
                    {
                        log.debug(
                            "CapabilityMap: useragent "
                                + userAgent
                                + "unknown, falling back to default");
                    }

                    // Use default Client
                    userAgent = DEFAULT_AGENT;
                }
            } else
            {
                // Found Client entry start populating the capability map.
                map = new CapabilityMapImpl();

                // Add client to CapabilityMap
                map.setClient(entry);

                // Add capabilities
                Iterator capabilities = entry.getCapabilities().iterator();
                while (capabilities.hasNext())
                {
                    map.addCapability((Capability) capabilities.next());
                }

                Collection mediatypes =
                    getMediaTypesForMimeTypes(entry.getMimetypes().iterator());

                // Add Mimetypes to map
                Iterator mimetypes = entry.getMimetypes().iterator();
                while (mimetypes.hasNext())
                {
                    map.addMimetype((MimeType) mimetypes.next());
                }

                Iterator media = mediatypes.iterator();
                while (media.hasNext())
                {
                    map.addMediaType((MediaType) media.next());
                }

                //Set preferred Mimetype
                MediaType mtEntry =
                    getMediaTypeForMimeType(map.getPreferredType().getName());

                map.setPreferredMediaType(mtEntry);

                // Add map to cache
                capabilityMapCache.put(userAgent, map);

                return map;
            }

        }
        
        if(map != null)
        {
               return map;
        }
        else
        {
            throw new UnableToBuildCapabilityMapException("We were unable to build a capability map for the agent, "+userAgent+
                                ".  This might be an indiciation that the capability database has not been correctly initialized.");
        }
    }

    /**
     * Returns the client which matches the given useragent string.
     *
     * @param useragent     the useragent to match
     * @return the found client or null if the user-agent does not match any
     *  defined client
     * @see org.apache.jetspeed.capabilities.CapabilityService#findClient(java.lang.String)
     */

    public Client findClient(String userAgent)
    {
        Client clientEntry = null;
        Iterator clients = getClients();

        if (log.isDebugEnabled())
        {
            log.debug(
                "ClientRegistry: Looking for client with useragent :"
                    + userAgent);
        }

        while (clients.hasNext())
        {
            Client client = (Client) clients.next();
            if (client.getUserAgentPattern() != null)
            {
                try
                {
                    // Java 1.4 has regular expressions build in
                    String exp = client.getUserAgentPattern();
                    //RE r = new RE(client.getUserAgentPattern());
                    //r.setMatchFlags(RE.MATCH_CASEINDEPENDENT);
                    //if (r.match(userAgent))
                    if (userAgent.matches(exp))
                    {

                        if (log.isDebugEnabled())
                        {
                            log.debug(
                                "Client: "
                                    + userAgent
                                    + " matches "
                                    + client.getUserAgentPattern());
                        }

                        return client;
                    } else
                    {
                        if (log.isDebugEnabled())
                        {
                            log.debug(
                                "Client: "
                                    + userAgent
                                    + " does not match "
                                    + client.getUserAgentPattern());
                        }
                    }
                } catch (java.util.regex.PatternSyntaxException e)
                {
                    String message =
                        "CapabilityServiceImpl: UserAgentPattern not valid : "
                            + client.getUserAgentPattern()
                            + " : "
                            + e.getMessage();
                    log.error(message, e);
                }
            }
        }

        return clientEntry;
    }

    /* 
     * @see org.apache.jetspeed.capabilities.CapabilityService#getClients()
     */
    public Iterator getClients()
    {
        if (null == clients)
        {
                
            this.clients = getPersistenceBrokerTemplate().getCollectionByQuery(
                    QueryFactory.newQuery(clientClass, new Criteria()));
        }

        return this.clients.iterator();
    }

    /* 
     * @see org.apache.jetspeed.capabilities.CapabilityService#getMediaTypesForMimeTypes(java.util.Iterator)
     */
    public Collection getMediaTypesForMimeTypes(Iterator mimetypes)
    {
        //Find the MediaType by matching the Mimetype
        
        Criteria filter = new Criteria();

        Vector temp = new Vector();
        // Add Mimetypes to map and create query
        while (mimetypes.hasNext())
        {
            MimeType mt = (MimeType) mimetypes.next();

            // Add mimetype to query
            // Note: mimetypes is a member of MediaTypeImpl
            // criteria.addEqualTo("mimetypes.name", mt.getName());
            //stuff.add(new Integer(mt.getMimetypeId()));
            temp.add(mt.getName());
        }
        
        Collection co = null;
        if (temp.size() > 0)
        {
            filter.addIn("mimetypes.name", temp);
            QueryByCriteria query = QueryFactory.newQuery(mediaTypeClass, filter);
            co = getPersistenceBrokerTemplate().getCollectionByQuery(query);            
        }

        if (co == null || co.isEmpty())
        {
            MediaType mt = getMediaType("html");
            Vector v = new Vector();
            v.add(mt);
            return v;
        }
        return co;
    }

    /* 
     * @see org.apache.jetspeed.capabilities.CapabilityService#deleteCapabilityMapCache()
     */
    public void deleteCapabilityMapCache()
    {
        capabilityMapCache.clear();
        clients = null;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.capabilities.CapabilityService#getMediaType(java.lang.String)
     */
    public MediaType getMediaType(String mediaType)
    {        
        Criteria filter = new Criteria();        
        filter.addEqualTo("name", mediaType);
        QueryByCriteria query = QueryFactory.newQuery(mediaTypeClass, filter);
        return (MediaType) getPersistenceBrokerTemplate().getObjectByQuery(query);                   
    }

    /**
     * getMediaTypeForMimeType
     * @param mimeType to use for lookup
     * @return MediaTypeEntry that matches the lookup in the MEDIATYPE_TO_MIMETYPE table
     */
    public MediaType getMediaTypeForMimeType(String mimeTypeName)
    {               
        //Find the MediaType by matching the Mimetype
                
        Criteria filter = new Criteria();       
        filter.addEqualTo("mimetypes.name", mimeTypeName);
        
        QueryByCriteria query = QueryFactory.newQuery(mediaTypeClass, filter);
        Collection mediaTypeCollection = getPersistenceBrokerTemplate().getCollectionByQuery(query);                    
        
        Iterator mtIterator = mediaTypeCollection.iterator();
        if (mtIterator.hasNext())
        {
            return (MediaType) mtIterator.next();
        } else
        {
            return null;
        }
    }
    
}
