/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/search/basic/ComparableResourceImpl.java,v 1.17 2005/02/28 17:25:44 luetzkendorf Exp $
 * $Revision: 1.17 $
 * $Date: 2005/02/28 17:25:44 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 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.slide.search.basic;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.slide.common.Domain;
import org.apache.slide.common.PropertyName;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.common.UriPath;
import org.apache.slide.content.Content;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeProperty.NamespaceCache;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.search.CompareHint;
import org.apache.slide.search.PropertyProvider;
import org.apache.slide.search.QueryScope;
import org.apache.slide.search.SearchToken;
import org.apache.slide.search.SlideUri;
import org.apache.slide.search.basic.Literals;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.ObjectNode;

/**
 * Represents one Resource. Implements operators like greaterThan,
 * equals, ...
 *
 * @version $Revision: 1.17 $
 */
public class ComparableResourceImpl implements ComparableResource {
    
    // TODO: properties with namespace...
    
    
    /** The ObjectNode of this resource */
    private ObjectNode objectNode;
    
    /** the latest revision of this resource */
    protected NodeRevisionDescriptor revisionDescriptor;
    
    private Content contentHelper;
    private NodeRevisionDescriptors revisionDescriptors;
    private SlideToken slideToken;
    private SearchToken searchToken;
    
    /**
     * The PropertyProvider to use (may be <code>null</code>).
     */
    protected PropertyProvider propertyProvider = null;
    
    /**
     * The scope.
     */
    protected QueryScope scope = null;
    
    
    /** coantains allowed on */
    static final private Set containsForbiddenOn = new HashSet();
    static {
        containsForbiddenOn.add ("image/gif");
        containsForbiddenOn.add ("image/jpeg");
        containsForbiddenOn.add ("image/pjpeg");
        containsForbiddenOn.add ("image/jpg");
        containsForbiddenOn.add ("audio/mpg");
        containsForbiddenOn.add ("audio/mpeg");
    };
    
    private static final int EQ =  1;
    private static final int GT =  2;
    private static final int LT =  3;
    private static final int GTE = 4;
    private static final int LTE = 5;
    
    
    
    /**
     * Constructs a RequestedResource.
     *
     * @param objectNode        the ObjectNode for this resource
     * @param searchToken       the SearchToken for this request
     * @param scope             the scope of the query.
     * @param propertyProvider  the  PropertyProvider to use (may be
     *                          <code>null</code>).
     *
     * @throws BadQueryException
     */
    public ComparableResourceImpl (ObjectNode objectNode, SearchToken searchToken, QueryScope scope, PropertyProvider propertyProvider)
        throws SlideException
    {
        this (objectNode, searchToken.getSlideToken(), searchToken.getContentHelper(), scope, propertyProvider);
        this.searchToken = searchToken;
    }
    
    
    // private boolean isCollection;
    
    /**
     * Constructs a RequestedResource.
     *
     * @param objectNode        the ObjectNode for this resource
     * @param slideToken        the slideToken for this request
     * @param contentHelper     you won't believe, the ContentHelper
     * @param scope             the scope of the query.
     * @param propertyProvider  the  PropertyProvider to use (may be
     *                          <code>null</code>).
     *
     * @throws BadQueryException
     */
    public ComparableResourceImpl (ObjectNode objectNode, SlideToken slideToken, Content contentHelper, QueryScope scope, PropertyProvider propertyProvider) throws SlideException {
        
        this.objectNode = objectNode;
        this.contentHelper = contentHelper;
        this.slideToken = slideToken;
        this.propertyProvider = propertyProvider;
        this.scope = scope;
        
        try {
            revisionDescriptors =
                contentHelper.retrieve (slideToken, objectNode.getUri());
            
            try {
                revisionDescriptor =
                    contentHelper.retrieve (slideToken, revisionDescriptors);
                
            }
            catch (org.apache.slide.content.RevisionDescriptorNotFoundException e) {
                // The object doesn't have any revision, we create a dummy
                // NodeRevisionDescriptor object
                // isCollection = true;
                revisionDescriptor = new NodeRevisionDescriptor(0);
                revisionDescriptor.setName(UriPath.getLastSegment(objectNode.getUri()));
            }
        }
        catch (AccessDeniedException e) {
            e.fillInStackTrace();
            throw e;
        }
        
        catch (Exception e) {
            e.printStackTrace();
            throw new SlideException (e.getMessage()); // TODO: clean exception handling
        }
    }
    protected ComparableResourceImpl () {}
    
    /**
     * Checks, if a property, represented by its name and value (as String),
     * is greater than the matching property within this item.
     *
     * @param    propName the name of the property to check
     * @param    literal  the value as String to check again
     *
     * @return   Literals.TRUE, Literals.FALSE or Literals.UNKNOWN
     *
     */
    public int greaterThan (String propName, String propNamespace, String literal) {
        return compare (propName, propNamespace, literal, GT);
    }
    
    public int greaterThanEquals (String propName, String propNamespace, String literal) {
        return compare (propName, propNamespace, literal, GTE);
    }
    
    public int lowerThan (String propName, String propNamespace, String literal) {
        return compare (propName, propNamespace, literal, LT);
    }
    
    public int lowerThanEquals (String propName, String propNamespace, String literal) {
        return compare (propName, propNamespace, literal, LTE);
    }
    
    /**
     * Checks, if a property, represented by its name and value (as String),
     * is EQUAL the matching property within this item.
     *
     * @param    propName the name of the property to check
     * @param    literal  the value as String to check again
     *
     * @return   Literals.TRUE, Literals.FALSE or Literals.UNKNOWN
     *
     */
    public int equals (String propName, String propNamespace, String literal) {
        return compare (propName, propNamespace, literal, EQ);
    }
    
    /**
     * checks, if a properties' value contains a literal
     *
     * @param    propName            a  String
     * @param    literal             a  String
     *
     * @return   Literal.TRUE, if propName's value contains literal
     *
     */
    public int propContains (String propName, String propNamespace, String literal) {
        Object o = getThisValue (propName, propNamespace);
        
        if (o == null) return Literals.FALSE;
        
        if (! (o instanceof String)) {
            // allow propcontains on XMLValues too
            o = o.toString();
        }
        
        String thisValue = (String)o;
        if (thisValue.indexOf(literal) == -1)
            return Literals.FALSE;
        
        return Literals.TRUE;
    }
    
    /**
     * href accessor
     *
     * @return   the href of this item
     */
    public String getInternalHref() {
        return objectNode.getUri();
    }
    
    /**
     * Method getExternalHref
     *
     * @return   a String
     * @deprecated
     */
    public String getExternalHref () {
        SlideUri slideContext = searchToken.getSlideContext();
        return  slideContext.getContextPath (objectNode.getUri());
    }
    
    
    /**
     * Method getThisValue
     *
     * @param    propName            the property name
     *
     * @return   the value of the property within this item
     */
    public Object getThisValue (String propName, String propNamespace) {
        Object result = null;
        // special handling for properties as contenlength, contenttype, ...
        
        // all other properties...
        NodeProperty property = null;
        try {
            property = getProperty(propName, propNamespace);
        }
        catch (SlideException e) {
            Domain.error("ComparableResourceImpl.getThisValue()", e);
        }
        result = (property == null) ? null : property.getValue();
        
        return result;
    }
    
    /**
     * Returns the NodeProperty with the given name and
     * {@link org.apache.slide.content.NodeProperty.NamespaceCache#DEFAULT_URI
     * default} namespace.
     *
     * @param      propName       the name of the property.
     *
     * @return     the requested NodeProperty.
     */
    protected NodeProperty getProperty(String propName) {
        NodeProperty property = null;
        try {
            property = getProperty(propName, NamespaceCache.DEFAULT_URI);
        }
        catch (SlideException e) {
            Domain.error("ComparableResourceImpl.getProperty()", e);
        }
        return property;
    }
    
    
    /**
     * Returns the URI of the resource.
     *
     * @return     the URI of the resource.
     */
    public String getUri() {
        return getInternalHref();
    }
    
    /**
     * Returns the property with the given <code>propertyName</code>.
     *
     * @param      propertyName       the PropertyName of the property.
     *
     * @return     the property with the given <code>npropertyNameame</code>.
     *
     * @throws     SlideException
     */
    public NodeProperty getProperty(PropertyName propertyName) throws SlideException {
        NodeProperty property = null;
        if ( (propertyProvider != null) && propertyProvider.isSupportedProperty(getUri(), propertyName) ) {
            property = propertyProvider.getProperty(getUri(), propertyName);
        }
        else {
            property = revisionDescriptor.getProperty(propertyName);
        }
        return property;
    }
    
    /**
     * Returns the property with the given <code>name</code> and
     * <code>namespace</code>.
     *
     * @param      name       the name of the property.
     * @param      namespace  the namespace URI of the property.
     *
     * @return     the property with the given <code>name</code> and
     *             <code>namespace</code>.
     *
     * @throws     SlideException
     */
    public NodeProperty getProperty(String name, String namespace) throws SlideException {
        
        NodeProperty property = null;
        if ( (propertyProvider != null) && propertyProvider.isSupportedProperty(getUri(), name, namespace) ) {
            property = propertyProvider.getProperty(getUri(), name, namespace);
        }
        else {
            property = revisionDescriptor.getProperty (name, namespace);
        }
        return property;
    }
    
    /**
     * Returns an Iterator of PropertyName of all properties.
     *
     * @return     an Iterator of PropertyName.
     *
     * @throws     SlideException
     */
    public Iterator getAllPropertiesNames() throws SlideException {
        
        Set propertyNamesSet = new HashSet();
        Enumeration props = revisionDescriptor.enumerateProperties();
        NodeProperty property = null;
        if (props != null) {
            while (props.hasMoreElements()) {
                property = (NodeProperty)props.nextElement();
                propertyNamesSet.add(property.getPropertyName());
            }
        }
        
        Iterator iterator = propertyProvider.getSupportedPropertiesNames(getUri());
        while (iterator.hasNext()) {
            propertyNamesSet.add(iterator.next());
        }
        
        return propertyNamesSet.iterator();
    }
    
    /**
     * Returns all properties as an Iterator of NodeProperty objects.
     *
     * @return     all properties as an Iterator of NodeProperty objects.
     *
     * @throws     SlideException
     */
    public Iterator getAllProperties() throws SlideException {
        
        Set propertySet = new HashSet();
        Enumeration props = revisionDescriptor.enumerateProperties();
        if (props != null) {
            while (props.hasMoreElements()) {
                propertySet.add(props.nextElement());
            }
        }
        
        if (propertyProvider != null) {
            Iterator iterator = propertyProvider.getSupportedProperties(getUri());
            while (iterator.hasNext()) {
                propertySet.add(iterator.next());
            }
        }
        
        return propertySet.iterator();
    }
    
    // brauchen wir die???
    /**
     * Method isCollection
     *
     * @param    revisionDescriptor  a  NodeRevisionDescriptor
     *
     * @return   a boolean
     *
     */
    public boolean isCollection (NodeRevisionDescriptor revisionDescriptor) {
        boolean result = false;
        
        if (revisionDescriptor == null)
            return true;
        
        NodeProperty property = revisionDescriptor.getProperty(PropertyName.RESOURCE_TYPE);
        
        if ((property != null)
            && (property.getValue().equals(NodeRevisionDescriptor.COLLECTION_TYPE))) {
            result = true;
        }
        
        return result;
    }
    
    
    /**
     * compares two RequestedResources according to OrderByHint. NULL values are
     * always considered as lessThan. (see [DASL] 5.6). Called in orderBy context
     * May only return 0 if the URIs are equal.
     *
     * @param    otherResource       a  RequestedResource
     * @param    hint                an OrderByHint
     *
     * @return   an int
     *
     */
    public int compareTo (ComparableResource otherResource, CompareHint hint) {
        int result = 0;
        
        Comparable otherValue = (Comparable) otherResource.getThisValue (hint.getPropName(), hint.getPropNamespace());
        Comparable thisValue = (Comparable) getThisValue (hint.getPropName(), hint.getPropNamespace());
        
        if (getInternalHref().equals (otherResource.getInternalHref()))
            result = 0;
            
        else if (thisValue != null && otherValue != null) {
            result = thisValue.compareTo(otherValue);
//            if (result == 0)
//                result = 1;
        }
        else if (thisValue == null)
            result = -1;
            
        else if (otherValue == null)
            result = 1;
        
        if (hint.isAscending() == false)
            result = result * -1;
        
        // todo: take casesensitive into account
        
        return result;
    }
    
    /**
     * Method isDefined
     *
     * @param    propName            a  String
     *
     * @return   true if propName is defined in this resource.
     *
     */
    public boolean isDefined (String propName, String propNamespace) {
        return getThisValue (propName, propNamespace) == null ? false : true;
    }
    
    /**
     * Checks, if the content of the resource contains a specific literal.
     *
     * @param    literal             a  String
     *
     * @return   true if literal is contained in this resources' content
     *
     */
    public int contains (String literal) {
        int result = Literals.UNKNOWN;
        
        NodeProperty property = getProperty("getcontenttype");
        
        if (property != null) {
            String contentType = (String) property.getValue();
            if (!containsForbiddenOn.contains (contentType)) {
                
                try {
                    NodeRevisionContent revisionContent = contentHelper.retrieve (slideToken, revisionDescriptors, revisionDescriptor);
                    
                    // TODO: be aware of I18N
                    
                    String content = new String (revisionContent.getContentBytes());
                    
                    if (content.indexOf (literal) > -1)
                        result = Literals.TRUE;
                    else
                        result = Literals.FALSE;
                }
                catch (org.apache.slide.common.SlideException e) {
                    // error handling ???
                }
                
            }
        }
        
        return result;
    }
    
    
    /**
     * Compares the value of this resources property (propname) to the
     * propname / literal pair. Returns an integer greater, equal or less than
     * 0 according java.lang.Comparable
     *
     * @param    propName            a  String
     * @param    literal             a  String
     *
     * @return   an int
     *
     * @throws   NumberFormatException
     * @throws   UnknownException
     *
     */
    private int compareTo (String propName, String propNamespace, String literal)
        throws NumberFormatException, UnknownException
    {
        int result = 0;
        
        Object thisValue = getThisValue (propName, propNamespace);
        if (thisValue == null)
            throw new UnknownException ();
        
        Object otherValue = null;
        
        otherValue = getOtherValue (thisValue, literal);
        result = ((Comparable)thisValue).compareTo((Comparable)otherValue);
        return result;
    }
    
    /**
     * Method getOtherValue
     *
     * @param    thisValue           an Object
     * @param    literal             a  String
     *
     * @return   an Object
     *
     * @throws   NumberFormatException
     */
    private Object getOtherValue (Object thisValue, String literal)
        throws NumberFormatException
    {
        Object otherValue = null;
        if (thisValue instanceof Boolean)
            otherValue = Boolean.valueOf(literal);
            
        else if (thisValue instanceof Date) {
            otherValue = new Date (); // TODO: dateTime.iso8601tz
            // otherValue = new Date (literal); // TODO: dateTime.iso8601tz
        }
            
        else if (thisValue instanceof Float)
            otherValue = new Float (literal);
            
        else if (thisValue instanceof Integer)
            otherValue = new Integer (literal);
            
        else if (thisValue instanceof Long)
            otherValue = new Long (literal);
            
        else
            otherValue = literal;
        return otherValue;
    }
    
    private int compare (String propName, String propNamespace, String literal, int op) {
        int result = Literals.FALSE;
        
//        Object thisValue = getThisValue (propName, propNamespace);
//        if (thisValue == null)
//            return Literals.UNKNOWN;
        
        //Object otherValue = null;
        
        try {
            //otherValue = getOtherValue (thisValue, literal);
            
            switch (op)
            {
                case EQ:
                    if (compareTo (propName, propNamespace, literal) == 0)
                        result = Literals.TRUE;
                    break;
                    
                case GT:
                    if (compareTo (propName, propNamespace, literal) > 0)
                        result = Literals.TRUE;
                    break;
                    
                case LT:
                    if (compareTo (propName, propNamespace, literal) < 0)
                        result = Literals.TRUE;
                    break;
                    
                case GTE:
                    if (compareTo (propName, propNamespace, literal) >= 0)
                        result = Literals.TRUE;
                    break;
                    
                case LTE:
                    if (compareTo (propName, propNamespace, literal) <= 0)
                        result = Literals.TRUE;
                    break;
                default:
                    break;
            }
        }
        catch (NumberFormatException e) {
            result = Literals.UNKNOWN;
        }
        catch (UnknownException e) {
            result = Literals.UNKNOWN;
        }
        return result;
    }
    
    public boolean equals (Object otherObject) {
        if (!(otherObject instanceof ComparableResource))
            return false;

        String otherUri = null;
        try {
            otherUri = ((ComparableResource)otherObject).getUri();
        } catch (SlideException e) {}

        if (getUri().equals (otherUri))
            return true;
        else
            return false;
    }

    public int hashCode () {
        return getUri().hashCode();
    }

    public String toString() {
        return getUri();
    }

    /**
     * Dummy Exception to indicate UNKNOWN state
     */
    private class UnknownException extends Exception {
    }
}

