/*
 *  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.ui.editors.entry;


import java.util.List;

import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.studio.entryeditors.EntryEditorInput;
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.DummyEntry;
import org.apache.directory.studio.ldapbrowser.core.utils.AttributeComparator;
import org.apache.directory.studio.ldapbrowser.core.utils.CompoundModification;
import org.apache.directory.studio.ldapbrowser.core.utils.ModelConverter;
import org.apache.directory.studio.ldapbrowser.core.utils.Utils;
import org.apache.directory.studio.ldapbrowser.ui.BrowserUIConstants;
import org.apache.directory.studio.ldapbrowser.ui.BrowserUIPlugin;
import org.apache.directory.studio.ldifeditor.editor.LdifDocumentProvider;
import org.apache.directory.studio.ldifparser.model.container.LdifContainer;
import org.apache.directory.studio.ldifparser.model.container.LdifContentRecord;
import org.apache.directory.studio.ldifparser.model.container.LdifInvalidContainer;
import org.apache.directory.studio.ldifparser.model.container.LdifRecord;
import org.apache.directory.studio.ldifparser.model.lines.LdifSepLine;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.osgi.util.NLS;


/**
 * The document provider for the LDIF entry editor.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class LdifEntryEditorDocumentProvider extends LdifDocumentProvider
{

    private EntryEditorInput input;

    private boolean inSetContent = false;

    private LdifEntryEditor editor;


    public LdifEntryEditorDocumentProvider( LdifEntryEditor editor )
    {
        this.editor = editor;
    }


    @Override
    protected void doSaveDocument( IProgressMonitor monitor, Object element, IDocument document, boolean overwrite )
        throws CoreException
    {
        LdifRecord[] records = getLdifModel().getRecords();
        if ( records.length != 1 || !( records[0] instanceof LdifContentRecord ) )
        {
            throw new CoreException( new Status( IStatus.ERROR, BrowserUIConstants.PLUGIN_ID, Messages
                .getString( "LdifEntryEditorDocumentProvider.InvalidRecordType" ) ) ); //$NON-NLS-1$
        }
        if ( !records[0].isValid() )
        {
            throw new CoreException( new Status( IStatus.ERROR, BrowserUIConstants.PLUGIN_ID, NLS.bind( Messages
                .getString( "LdifEntryEditorDocumentProvider.InvalidLdif" ), records[0].getInvalidString() ) ) ); //$NON-NLS-1$
        }
        for ( LdifContainer ldifContainer : getLdifModel().getContainers() )
        {
            if ( ldifContainer instanceof LdifInvalidContainer )
            {
                LdifInvalidContainer cont = ( LdifInvalidContainer ) ldifContainer;
                throw new CoreException( new Status( IStatus.ERROR, BrowserUIConstants.PLUGIN_ID, NLS.bind( Messages
                    .getString( "LdifEntryEditorDocumentProvider.InvalidLdif" ), cont.getInvalidString() ) ) ); //$NON-NLS-1$
            }
        }

        EntryEditorInput input = getEntryEditorInput( element );
        try
        {
            Dn newDn = new Dn( records[0].getDnLine().getValueAsString() );
            if ( !newDn.equals( input.getResolvedEntry().getDn() ) )
            {
                throw new CoreException( new Status( IStatus.ERROR, BrowserUIConstants.PLUGIN_ID, NLS.bind( Messages
                    .getString( "LdifEntryEditorDocumentProvider.ModDnNotSupported" ), records[0].getInvalidString() ) ) ); //$NON-NLS-1$
            }
        }
        catch ( LdapInvalidDnException e )
        {
            throw new CoreException( new Status( IStatus.ERROR, BrowserUIConstants.PLUGIN_ID, Messages
                .getString( "LdifEntryEditorDocumentProvider.InvalidDN" ) ) ); //$NON-NLS-1$
        }

        IStatus status = input.saveSharedWorkingCopy( false, editor );
        if ( status != null && !status.isOK() )
        {
            BrowserUIPlugin.getDefault().getLog().log( status );
            throw new CoreException( status );
        }
    }


    @Override
    public void documentChanged( DocumentEvent event )
    {
        super.documentChanged( event );

        // the document change was caused by the model update
        // no need to update the model again, don't fire more events
        if ( inSetContent )
        {
            return;
        }

        // only continue if the LDIF model is valid
        LdifRecord[] records = getLdifModel().getRecords();
        if ( records.length != 1 || !( records[0] instanceof LdifContentRecord ) || !records[0].isValid()
            || !records[0].getDnLine().isValid() )
        {
            return;
        }
        for ( LdifContainer ldifContainer : getLdifModel().getContainers() )
        {
            if ( ldifContainer instanceof LdifInvalidContainer )
            {
                return;
            }
        }

        // update shared working copy
        try
        {
            LdifContentRecord modifiedRecord = ( LdifContentRecord ) records[0];
            IBrowserConnection browserConnection = input.getSharedWorkingCopy( editor ).getBrowserConnection();
            DummyEntry modifiedEntry = ModelConverter.ldifContentRecordToEntry( modifiedRecord, browserConnection );
            ( ( DummyEntry ) input.getSharedWorkingCopy( editor ) ).setDn( modifiedEntry.getDn() );
            new CompoundModification().replaceAttributes( modifiedEntry, input.getSharedWorkingCopy( editor ), this );
        }
        catch ( LdapInvalidDnException e )
        {
            throw new RuntimeException( e );
        }
    }


    @Override
    protected void doResetDocument( Object element, IProgressMonitor monitor ) throws CoreException
    {
        // reset working copy first
        if ( input != null )
        {
            input.resetSharedWorkingCopy( editor );
        }

        super.doResetDocument( element, monitor );
    }


    public void workingCopyModified( EntryEditorInput input, Object source )
    {
        // the model change was caused by the document change
        // no need to set the content again, don't fire more events
        if ( source == this )
        {
            return;
        }

        IDocument document = getDocument( input );
        if ( document != null )
        {
            try
            {
                inSetContent = true;
                IEntry sharedWorkingCopy = input.getSharedWorkingCopy( editor );
                setDocumentInput( document, sharedWorkingCopy );

                // reset dirty state
                if ( !input.isSharedWorkingCopyDirty( editor ) )
                {
                    super.doResetDocument( input, null );
                }
            }
            catch ( CoreException e )
            {
                throw new RuntimeException( e );
            }
            finally
            {
                inSetContent = false;
            }
        }
    }


    private void setDocumentInput( IDocument document, IEntry entry )
    {
        // sort attribute-value lines
        List<IValue> sortedValues = AttributeComparator.toSortedValues( entry );
        LdifContentRecord newRecord = LdifContentRecord.create( entry.getDn().getName() );
        for ( IValue value : sortedValues )
        {
            newRecord.addAttrVal( ModelConverter.valueToLdifAttrValLine( value ) );
        }
        newRecord.finish( LdifSepLine.create() );

        // format
        String newContent = newRecord.toFormattedString( Utils.getLdifFormatParameters() );

        // set content
        document.set( newContent );
    }


    public IDocument getDocument( Object element )
    {
        if ( element instanceof EntryEditorInput )
        {
            EntryEditorInput input = ( EntryEditorInput ) element;
            if ( input.getExtension() == null )
            {
                // this is a performance optimization
                return null;
            }
        }

        return super.getDocument( element );
    }


    @Override
    protected IDocument createDocument( Object element ) throws CoreException
    {
        input = getEntryEditorInput( element );
        IEntry entry = getEntryEditorInput( element ).getSharedWorkingCopy( editor );
        IDocument document = new Document();
        if ( entry != null )
        {
            setDocumentInput( document, entry );
        }
        setupDocument( document );
        return document;
    }


    private EntryEditorInput getEntryEditorInput( Object element ) throws CoreException
    {
        if ( element instanceof EntryEditorInput )
        {
            EntryEditorInput input = ( EntryEditorInput ) element;
            return input;
        }
        else
        {
            throw new CoreException( new Status( IStatus.ERROR, BrowserUIConstants.PLUGIN_ID,
                "Expected EntryEditorInput, was " + element ) ); //$NON-NLS-1$
        }
    }


    @Override
    public boolean isModifiable( Object element )
    {
        if ( element instanceof EntryEditorInput )
        {
            EntryEditorInput editorInput = ( EntryEditorInput ) element;
            IEntry entry = editorInput.getSharedWorkingCopy( editor );
            return ( entry != null );
        }

        return false;
    }
}
