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


import java.util.Collection;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
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.events.ValueMultiModificationEvent;
import org.apache.directory.studio.ldapbrowser.core.events.ValueRenamedEvent;
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.impl.Attribute;
import org.apache.directory.studio.ldapbrowser.core.model.impl.Value;
import org.apache.directory.studio.ldifparser.model.container.LdifContentRecord;


/**
 * Performs compound operations to model classes.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class CompoundModification
{

    /**
     * Rename the values by removing the old attribute from the entry and adding 
     * a new attribute to the entry. Only one event (an {@link ValueRenamedEvent}) is fired.
     * 
     * @param oldValues the old values
     * @param newAttributeDescription the new attribute description
     */
    public void renameValues( IValue[] oldValues, String newAttributeDescription )
    {
        if ( ArrayUtils.isEmpty( oldValues ) )
        {
            throw new IllegalArgumentException( "Expected non-null and non-empty values array." ); //$NON-NLS-1$
        }
        if ( StringUtils.isEmpty( newAttributeDescription ) )
        {
            throw new IllegalArgumentException( "Expected non-null and non-empty attribute description." ); //$NON-NLS-1$
        }

        if ( newAttributeDescription != null && !"".equals( newAttributeDescription ) //$NON-NLS-1$
            && !newAttributeDescription.equals( oldValues[0].getAttribute().getDescription() ) )
        {
            ValueRenamedEvent event = null;
            try
            {
                EventRegistry.suspendEventFiringInCurrentThread();
                for ( IValue oldValue : oldValues )
                {
                    if ( !newAttributeDescription.equals( oldValue.getAttribute().getDescription() ) )
                    {
                        IAttribute oldAttribute = oldValue.getAttribute();
                        IEntry entry = oldAttribute.getEntry();
                        IValue newValue = null;

                        // delete old value
                        oldAttribute.deleteValue( oldValue );
                        if ( oldAttribute.getValueSize() == 0 )
                        {
                            entry.deleteAttribute( oldAttribute );
                        }

                        // add new value
                        IAttribute attribute = entry.getAttribute( newAttributeDescription );
                        if ( attribute == null )
                        {
                            attribute = new Attribute( entry, newAttributeDescription );
                            entry.addAttribute( attribute );
                        }
                        newValue = new Value( attribute, oldValue.getRawValue() );
                        attribute.addValue( newValue );

                        // prepare event
                        if ( event == null )
                        {
                            event = new ValueRenamedEvent( entry.getBrowserConnection(), entry, oldValue, newValue );
                        }
                    }
                }
            }
            finally
            {
                EventRegistry.resumeEventFiringInCurrentThread();
            }

            // fire events
            EventRegistry.fireEntryUpdated( event, this );
        }
    }


    /**
     * Deletes the values and the attribute if no values remain.
     * Only one event (an {@link ValueDeletedEvent}) is fired.
     *
     * @param values
     *      the Values to delete
     */
    public void deleteValues( Collection<IValue> values )
    {
        if ( CollectionUtils.isEmpty( values ) )
        {
            throw new IllegalArgumentException( "Expected non-null and non-empty values collection." ); //$NON-NLS-1$
        }

        ValueDeletedEvent event = null;
        try
        {
            EventRegistry.suspendEventFiringInCurrentThread();
            for ( IValue value : values )
            {
                IAttribute attribute = value.getAttribute();
                IEntry entry = attribute.getEntry();

                attribute.deleteValue( value );
                if ( event == null )
                {
                    event = new ValueDeletedEvent( entry.getBrowserConnection(), entry, attribute, value );
                }

                if ( attribute.getValueSize() == 0 )
                {
                    attribute.getEntry().deleteAttribute( attribute );
                }
            }
        }
        finally
        {
            EventRegistry.resumeEventFiringInCurrentThread();
        }

        // fire event
        EventRegistry.fireEntryUpdated( event, this );
    }


    /**
     * Modifies the value and sets the given raw value.
     * Only one event (an {@link ValueAddEvent} or an {@link ValueModifiedEvent}) is fired.
     * 
     * @param oldValue the old value
     * @param newRawValue the new raw value
     */
    public void modifyValue( IValue oldValue, Object newRawValue )
    {
        if ( oldValue == null || newRawValue == null )
        {
            throw new IllegalArgumentException( "Expected non-null value." ); //$NON-NLS-1$
        }

        IAttribute attribute = oldValue.getAttribute();

        boolean modify = false;
        if ( oldValue != null && newRawValue instanceof byte[] )
        {
            byte[] newValue = ( byte[] ) newRawValue;
            if ( !Utils.equals( oldValue.getBinaryValue(), newValue ) )
            {
                modify = true;
            }
        }
        else if ( oldValue != null && newRawValue instanceof String )
        {

            String newValue = ( String ) newRawValue;
            if ( !oldValue.getStringValue().equals( newValue ) )
            {
                modify = true;
            }
        }

        if ( modify )
        {
            if ( oldValue.isEmpty() )
            {
                EventRegistry.suspendEventFiringInCurrentThread();
                attribute.deleteEmptyValue();
                EventRegistry.resumeEventFiringInCurrentThread();

                Value value = new Value( attribute, newRawValue );
                attribute.addValue( value );
            }
            else
            {
                IValue newValue = new Value( attribute, newRawValue );
                attribute.modifyValue( oldValue, newValue );
            }
        }
    }


    /**
     * Creates the attribute with the given value in the entry.
     * Only one event (an {@link ValueAddedEvent}) is fired.
     * 
     * @param entry the entry
     * @param attributeDescription the attribute description
     * @param newRawValue the new raw value
     * 
     * @throws ModelModificationException the model modification exception
     */
    public void createValue( IEntry entry, String attributeDescription, Object newRawValue )
    {
        if ( entry == null )
        {
            throw new IllegalArgumentException( "Expected non-null entry." ); //$NON-NLS-1$
        }
        if ( StringUtils.isEmpty( attributeDescription ) )
        {
            throw new IllegalArgumentException( "Expected non-null and non-empty attribute description." ); //$NON-NLS-1$
        }
        if ( newRawValue == null )
        {
            throw new IllegalArgumentException( "Expected non-null value." ); //$NON-NLS-1$
        }

        IAttribute attribute = entry.getAttribute( attributeDescription );
        if ( attribute == null )
        {
            EventRegistry.suspendEventFiringInCurrentThread();
            attribute = new Attribute( entry, attributeDescription );
            entry.addAttribute( attribute );
            EventRegistry.resumeEventFiringInCurrentThread();
        }

        Value value = new Value( attribute, newRawValue );
        attribute.addValue( value );
    }


    /**
     * Creates the attribute with the given value in the entry.
     * Only one event (an {@link ValueAddedEvent}) is fired.
     * 
     * @param entry the entry
     * @param values the values
     * 
     * @throws ModelModificationException the model modification exception
     */
    public void createValues( IEntry entry, IValue... values )
    {
        if ( entry == null )
        {
            throw new IllegalArgumentException( "Expected non-null entry." ); //$NON-NLS-1$
        }
        if ( ArrayUtils.isEmpty( values ) )
        {
            throw new IllegalArgumentException( "Expected non-null and non-empty values array." ); //$NON-NLS-1$
        }

        ValueAddedEvent event = null;
        EventRegistry.suspendEventFiringInCurrentThread();
        for ( IValue value : values )
        {
            String attributeDescription = value.getAttribute().getDescription();
            IAttribute attribute = entry.getAttribute( attributeDescription );
            if ( attribute == null )
            {
                attribute = new Attribute( entry, attributeDescription );
                entry.addAttribute( attribute );
            }
            Value newValue = new Value( attribute, value.getRawValue() );
            attribute.addValue( newValue );
            if ( event == null )
            {
                event = new ValueAddedEvent( entry.getBrowserConnection(), entry, attribute, newValue );
            }
        }
        EventRegistry.resumeEventFiringInCurrentThread();

        // fire event
        EventRegistry.fireEntryUpdated( event, this );
    }


    /**
     * Copies all attributes and values from the 1st entry to the second entry.
     * Clears all existing attributes from the 2nd entry.
     * Only one event (an {@link ValueMultiModificationEvent}) is fired.
     *
     * @param fromEntry
     * @param toEntry
     */
    public void replaceAttributes( IEntry fromEntry, IEntry toEntry, Object source )
    {
        EventRegistry.suspendEventFiringInCurrentThread();
        for ( IAttribute attribute : toEntry.getAttributes() )
        {
            toEntry.deleteAttribute( attribute );
        }

        // create new attributes
        for ( IAttribute attribute : fromEntry.getAttributes() )
        {
            IAttribute newAttribute = new Attribute( toEntry, attribute.getDescription() );
            for ( IValue value : attribute.getValues() )
            {
                IValue newValue = new Value( newAttribute, value.getRawValue() );
                newAttribute.addValue( newValue );
            }
            toEntry.addAttribute( newAttribute );
        }
        EventRegistry.resumeEventFiringInCurrentThread();

        ValueMultiModificationEvent event = new ValueMultiModificationEvent( toEntry.getBrowserConnection(), toEntry );
        EventRegistry.fireEntryUpdated( event, source );
    }


    /**
     * Clones an entry, no event is fired.
     * 
     * @param entry the entry
     * 
     * @return the cloned entry
     */
    public IEntry cloneEntry( IEntry entry )
    {
        try
        {
            EventRegistry.suspendEventFiringInCurrentThread();
            IBrowserConnection browserConnection = entry.getBrowserConnection();
            LdifContentRecord record = ModelConverter.entryToLdifContentRecord( entry );
            IEntry clonedEntry = ModelConverter.ldifContentRecordToEntry( record, browserConnection );
            return clonedEntry;
        }
        catch ( LdapInvalidDnException e )
        {
            throw new RuntimeException( e );
        }
        finally
        {
            EventRegistry.resumeEventFiringInCurrentThread();
        }
    }

}
