/*
 *  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.directory.studio.ldapbrowser.core.model.impl;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.studio.connection.core.Connection;
import org.apache.directory.studio.ldapbrowser.core.BrowserCoreMessages;
import org.apache.directory.studio.ldapbrowser.core.events.EmptyValueAddedEvent;
import org.apache.directory.studio.ldapbrowser.core.events.EmptyValueDeletedEvent;
import org.apache.directory.studio.ldapbrowser.core.events.EntryModificationEvent;
import org.apache.directory.studio.ldapbrowser.core.events.EventRegistry;
import org.apache.directory.studio.ldapbrowser.core.events.ValueAddedEvent;
import org.apache.directory.studio.ldapbrowser.core.events.ValueDeletedEvent;
import org.apache.directory.studio.ldapbrowser.core.events.ValueModifiedEvent;
import org.apache.directory.studio.ldapbrowser.core.internal.search.LdapSearchPageScoreComputer;
import org.apache.directory.studio.ldapbrowser.core.model.AttributeDescription;
import org.apache.directory.studio.ldapbrowser.core.model.IAttribute;
import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection;
import org.apache.directory.studio.ldapbrowser.core.model.IEntry;
import org.apache.directory.studio.ldapbrowser.core.model.IValue;
import org.apache.directory.studio.ldapbrowser.core.model.schema.SchemaUtils;
import org.eclipse.search.ui.ISearchPageScoreComputer;


/**
 * Default implementation of IAttribute.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class Attribute implements IAttribute
{

    /** The serialVersionUID. */
    private static final long serialVersionUID = -5679384884002589786L;

    /** The attribute description */
    private AttributeDescription attributeDescription;

    /** The entry this attribute belongs to */
    private IEntry entry;

    /** The values */
    private List<IValue> valueList;


    /**
     * Creates an new instance of Attribute with the given description
     * and no value.
     * 
     * @param entry
     *                The entry of this attribute, mustn't be null
     * @param description
     *                The attribute descrption, mustn't be null.
     */
    public Attribute( IEntry entry, String description )
    {
        assert entry != null;
        assert description != null;

        this.entry = entry;
        this.attributeDescription = new AttributeDescription( description );
        this.valueList = new ArrayList<IValue>();

    }


    /**
     * {@inheritDoc}
     */
    public IEntry getEntry()
    {
        return entry;
    }


    /**
     * {@inheritDoc}
     */
    public boolean isConsistent()
    {
        if ( valueList.isEmpty() )
        {
            return false;
        }

        for ( IValue value : valueList )
        {
            if ( value.isEmpty() )
            {
                return false;
            }
        }

        return true;
    }


    /**
     * {@inheritDoc}
     */
    public boolean isMustAttribute()
    {
        if ( isObjectClassAttribute() )
        {
            return true;
        }
        else
        {
            Collection<AttributeType> mustAtds = SchemaUtils.getMustAttributeTypeDescriptions( entry );
            return mustAtds.contains( getAttributeTypeDescription() );
        }
    }


    /**
     * {@inheritDoc}
     */
    public boolean isMayAttribute()
    {
        return !isObjectClassAttribute() && !isMustAttribute() && !isOperationalAttribute();
    }


    /**
     * {@inheritDoc}
     */
    public boolean isOperationalAttribute()
    {
        return getAttributeTypeDescription() == null || SchemaUtils.isOperational( getAttributeTypeDescription() );
    }


    /**
     * {@inheritDoc}
     */
    public boolean isObjectClassAttribute()
    {
        return SchemaConstants.OBJECT_CLASS_AT.equalsIgnoreCase( getDescription() );
    }


    /**
     * {@inheritDoc}
     */
    public boolean isString()
    {
        return !isBinary();
    }


    /**
     * {@inheritDoc}
     */
    public boolean isBinary()
    {
        return SchemaUtils.isBinary( getAttributeTypeDescription(), getEntry().getBrowserConnection().getSchema() );
    }


    /**
     * {@inheritDoc}
     */
    public void addEmptyValue()
    {
        IValue emptyValue = new Value( this );
        valueList.add( emptyValue );
        attributeModified( new EmptyValueAddedEvent( getEntry().getBrowserConnection(), getEntry(), this, emptyValue ) );
    }


    /**
     * {@inheritDoc}
     */
    public void deleteEmptyValue()
    {
        for ( Iterator<IValue> it = this.valueList.iterator(); it.hasNext(); )
        {
            IValue value = it.next();
            if ( value.isEmpty() )
            {
                it.remove();
                attributeModified( new EmptyValueDeletedEvent( getEntry().getBrowserConnection(), getEntry(), this,
                    value ) );
                return;
            }
        }
    }


    /**
     * Fires an EntryModificationEvent.
     *
     * @param event the EntryModificationEvent
     */
    private void attributeModified( EntryModificationEvent event )
    {
        EventRegistry.fireEntryUpdated( event, getEntry() );
    }


    /**
     * Checks if the given value is valid.
     *
     * @param value the value to check
     * @throws IllegalArgumentException if the value is not valid
     */
    private void checkValue( IValue value ) throws IllegalArgumentException
    {
        if ( value == null )
        {
            throw new IllegalArgumentException( BrowserCoreMessages.model__empty_value );
        }
        if ( !value.getAttribute().equals( this ) )
        {
            throw new IllegalArgumentException( BrowserCoreMessages.model__values_attribute_is_not_myself );
        }
    }


    /**
     * Deletes the given value from value list.
     *
     * @param valueToDelete the value to delete
     * @return true if deleted
     */
    private boolean internalDeleteValue( IValue valueToDelete )
    {
        for ( Iterator<IValue> it = valueList.iterator(); it.hasNext(); )
        {
            IValue value = it.next();
            if ( value.equals( valueToDelete ) )
            {
                it.remove();
                return true;
            }
        }
        return false;
    }


    /**
     * {@inheritDoc}
     */
    public void addValue( IValue valueToAdd ) throws IllegalArgumentException
    {
        checkValue( valueToAdd );
        valueList.add( valueToAdd );
        attributeModified( new ValueAddedEvent( getEntry().getBrowserConnection(), getEntry(), this, valueToAdd ) );
    }


    /**
     * {@inheritDoc}
     */
    public void deleteValue( IValue valueToDelete ) throws IllegalArgumentException
    {
        checkValue( valueToDelete );

        if ( internalDeleteValue( valueToDelete ) )
        {
            attributeModified( new ValueDeletedEvent( getEntry().getBrowserConnection(), getEntry(), this,
                valueToDelete ) );
        }
    }


    /**
     * {@inheritDoc}
     */
    public void modifyValue( IValue oldValue, IValue newValue ) throws IllegalArgumentException
    {
        checkValue( oldValue );
        checkValue( newValue );

        internalDeleteValue( oldValue );
        valueList.add( newValue );
        attributeModified( new ValueModifiedEvent( getEntry().getBrowserConnection(), getEntry(), this, oldValue,
            newValue ) );
    }


    /**
     * {@inheritDoc}
     */
    public IValue[] getValues()
    {
        return ( IValue[] ) valueList.toArray( new IValue[0] );
    }


    /**
     * {@inheritDoc}
     */
    public int getValueSize()
    {
        return valueList.size();
    }


    /**
     * {@inheritDoc}
     */
    public String getDescription()
    {
        return getAttributeDescription().getDescription();
    }


    /**
     * {@inheritDoc}
     */
    public String getType()
    {
        return getAttributeDescription().getParsedAttributeType();
    }


    /**
     * {@inheritDoc}
     */
    public String toString()
    {
        return getDescription();
    }


    /**
     * {@inheritDoc}
     */
    public boolean equals( Object o )
    {
        // check argument
        if ( !( o instanceof IAttribute ) )
        {
            return false;
        }
        IAttribute a = ( IAttribute ) o;

        // compare entries
        if ( !getEntry().equals( a.getEntry() ) )
        {
            return false;
        }

        // compare attribute description
        return getDescription().equals( a.getDescription() );
    }


    /**
     * {@inheritDoc}
     */
    public int hashCode()
    {
        return getDescription().hashCode();
    }


    /**
     * {@inheritDoc}
     */
    public byte[][] getBinaryValues()
    {
        List<byte[]> binaryValueList = new ArrayList<byte[]>();

        IValue[] values = getValues();
        for ( IValue value : values )
        {
            binaryValueList.add( value.getBinaryValue() );
        }

        return binaryValueList.toArray( new byte[0][] );
    }


    /**
     * {@inheritDoc}
     */
    public String getStringValue()
    {
        if ( getValueSize() > 0 )
        {
            return ( ( IValue ) valueList.get( 0 ) ).getStringValue();
        }
        else
        {
            return null;
        }
    }


    /**
     * {@inheritDoc}
     */
    public String[] getStringValues()
    {
        List<String> stringValueList = new ArrayList<String>();

        IValue[] values = getValues();
        for ( IValue value : values )
        {
            stringValueList.add( value.getStringValue() );
        }

        return stringValueList.toArray( new String[stringValueList.size()] );
    }


    /**
     * {@inheritDoc}
     */
    public AttributeType getAttributeTypeDescription()
    {
        return getEntry().getBrowserConnection().getSchema().getAttributeTypeDescription( getType() );
    }


    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public Object getAdapter( Class adapter )
    {
        Class<?> clazz = ( Class<?> ) adapter;
        if ( clazz.isAssignableFrom( ISearchPageScoreComputer.class ) )
        {
            return new LdapSearchPageScoreComputer();
        }
        if ( clazz.isAssignableFrom( Connection.class ) )
        {
            return getEntry().getBrowserConnection().getConnection();
        }
        if ( clazz.isAssignableFrom( IBrowserConnection.class ) )
        {
            return getEntry().getBrowserConnection();
        }
        if ( clazz.isAssignableFrom( IEntry.class ) )
        {
            return getEntry();
        }
        if ( clazz.isAssignableFrom( IAttribute.class ) )
        {
            return this;
        }

        return null;
    }


    /**
     * {@inheritDoc}
     */
    public AttributeDescription getAttributeDescription()
    {
        return attributeDescription;
    }

}
