/*
 *  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.apacheds.configuration.jobs;


import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.apache.directory.api.ldap.model.constants.LdapConstants;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.server.config.ConfigPartitionInitializer;
import org.apache.directory.server.config.ConfigPartitionReader;
import org.apache.directory.server.config.ReadOnlyConfigurationPartition;
import org.apache.directory.server.config.beans.ConfigBean;
import org.apache.directory.server.constants.ServerDNConstants;
import org.apache.directory.server.core.api.DnFactory;
import org.apache.directory.server.core.api.InstanceLayout;
import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
import org.apache.directory.server.core.partition.ldif.LdifPartition;
import org.apache.directory.studio.apacheds.configuration.ApacheDS2ConfigurationPlugin;
import org.apache.directory.studio.apacheds.configuration.ApacheDS2ConfigurationPluginConstants;
import org.apache.directory.studio.apacheds.configuration.editor.Configuration;
import org.apache.directory.studio.apacheds.configuration.editor.ConnectionServerConfigurationInput;
import org.apache.directory.studio.apacheds.configuration.editor.NewServerConfigurationInput;
import org.apache.directory.studio.apacheds.configuration.editor.ServerConfigurationEditor;
import org.apache.directory.studio.common.core.jobs.StudioProgressMonitor;
import org.apache.directory.studio.common.core.jobs.StudioRunnableWithProgress;
import org.apache.directory.studio.connection.core.Connection;
import org.apache.directory.studio.connection.core.ConnectionCorePlugin;
import org.apache.directory.studio.connection.core.IConnectionListener;
import org.apache.directory.studio.connection.core.event.ConnectionEventRegistry;
import org.apache.directory.studio.connection.core.io.api.StudioSearchResult;
import org.apache.directory.studio.connection.core.io.api.StudioSearchResultEnumeration;
import org.apache.directory.studio.ldapbrowser.core.BrowserCorePlugin;
import org.apache.directory.studio.ldapbrowser.core.jobs.SearchRunnable;
import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection;
import org.apache.directory.studio.ldapbrowser.core.model.SearchParameter;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.part.FileEditorInput;
import org.osgi.framework.Bundle;


/**
 * This class implements a {@link Job} that is used to load a server configuration.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class LoadConfigurationRunnable implements StudioRunnableWithProgress
{
    /** The associated editor */
    private ServerConfigurationEditor editor;


    /**
     * Creates a new instance of LoadConfigurationRunnable.
     * 
     * @param editor the editor
     */
    public LoadConfigurationRunnable( ServerConfigurationEditor editor )
    {
        super();
        this.editor = editor;
    }


    /**
     * {@inheritDoc}
     */
    public String getErrorMessage()
    {
        return Messages.getString( "LoadConfigurationRunnable.UnableToLoadConfiguration" ); //$NON-NLS-1$
    }


    /**
     * {@inheritDoc}
     */
    public Object[] getLockedObjects()
    {
        return new Object[0];
    }


    /**
     * {@inheritDoc}
     */
    public String getName()
    {
        return Messages.getString( "LoadConfigurationRunnable.LoadConfiguration" ); //$NON-NLS-1$
    }


    /**
     * {@inheritDoc}
     */
    public void run( StudioProgressMonitor monitor )
    {
        IEditorInput input = editor.getEditorInput();

        try
        {
            final Configuration configuration = getConfiguration( input, monitor );
            
            if ( configuration != null )
            {
                Display.getDefault().asyncExec( new Runnable()
                {
                    public void run()
                    {
                        editor.configurationLoaded( configuration );
                    }
                } );
            }
        }
        catch ( Exception e )
        {
            ApacheDS2ConfigurationPlugin.getDefault().getLog().log( 
                new Status( Status.ERROR, "org.apache.directory.studio.apacheds.configuration", 
                    e.getMessage() ) );

            // Reporting the error to the monitor
            monitor.reportError( e );

            // Reporting the error to the editor
            final Exception exception = e;
            
            Display.getDefault().asyncExec( new Runnable()
            {
                public void run()
                {
                    editor.configurationLoadFailed( exception );
                }
            } );
        }
    }


    /**
     * Gets the configuration from the input.
     * 
     * @param input the editor input
     * @param monitor the studio progress monitor
     * @return the configuration
     * @throws Exception If the configuration wasn't correctly read
     */
    public Configuration getConfiguration( IEditorInput input, StudioProgressMonitor monitor ) throws Exception
    {
        String inputClassName = input.getClass().getName();
        
        // If the input is a NewServerConfigurationInput, then we only 
        // need to get the server configuration and return
        if ( input instanceof NewServerConfigurationInput )
        {
            Bundle bundle = Platform.getBundle( "org.apache.directory.server.config" );
            URL resource = bundle.getResource( "config.ldif" );
            InputStream is = resource.openStream();
            return readSingleFileConfiguration( is );
        }

        // If the input is a ConnectionServerConfigurationInput, then we 
        // read the server configuration from the selected connection
        if ( input instanceof ConnectionServerConfigurationInput )
        {
            return readConfiguration( ( ConnectionServerConfigurationInput ) input, monitor );
        }
        else if ( input instanceof FileEditorInput )
        // The 'FileEditorInput' class is used when the file is opened
        // from a project in the workspace.
        {
            File file = ( ( FileEditorInput ) input ).getFile().getLocation().toFile();
            return readConfiguration( file );
        }
        else if ( input instanceof IPathEditorInput )
        {
            File file = ( ( IPathEditorInput ) input ).getPath().toFile();
            return readConfiguration( file );
        }
        else if ( inputClassName.equals( "org.eclipse.ui.internal.editors.text.JavaFileEditorInput" ) //$NON-NLS-1$
            || inputClassName.equals( "org.eclipse.ui.ide.FileStoreEditorInput" ) ) //$NON-NLS-1$
        // The class 'org.eclipse.ui.internal.editors.text.JavaFileEditorInput'
        // is used when opening a file from the menu File > Open... in Eclipse 3.2.x
        // The class 'org.eclipse.ui.ide.FileStoreEditorInput' is used when
        // opening a file from the menu File > Open... in Eclipse 3.3.x
        {
            // We use the tooltip to get the full path of the file
            File file = new File( input.getToolTipText() );
            return readConfiguration( file );
        }

        return null;
    }


    /**
     * Reads the configuration from the given input stream.
     *
     * @param is the input stream
     * @return the associated configuration bean
     * @throws Exception if we weren't able to load the configuration
     */
    public static Configuration readConfiguration( File file ) throws Exception
    {
        if ( file != null )
        {
            if(file.getName().equals( ApacheDS2ConfigurationPluginConstants.CONFIG_LDIF )) {
                return readSingleFileConfiguration( file );
            }
            else if(file.getName().equals( ApacheDS2ConfigurationPluginConstants.OU_CONFIG_LDIF )) {
                return readMultiFileConfigureation( file.getParentFile() );
            }
        }

        return null;
    }


    private static Configuration readSingleFileConfiguration( File configLdifFile ) throws Exception
    {
        InputStream is = new FileInputStream( configLdifFile );

        // Reading the configuration partition
        return readSingleFileConfiguration( is );
    }


    private static Configuration readSingleFileConfiguration( InputStream is ) throws Exception
    {
        // Creating a partition associated from the input stream
        ReadOnlyConfigurationPartition configurationPartition = new ReadOnlyConfigurationPartition( is,
            ApacheDS2ConfigurationPlugin.getDefault().getSchemaManager() );

        configurationPartition.initialize();

        // Reading the configuration partition
        return readConfiguration( configurationPartition );
    }


    private static synchronized Configuration readMultiFileConfigureation( File confDirectory ) throws Exception
    {
        InstanceLayout instanceLayout = new InstanceLayout( confDirectory.getParentFile() );

        SchemaManager schemaManager = ApacheDS2ConfigurationPlugin.getDefault().getSchemaManager();

        DnFactory dnFactory = null;

        ConfigPartitionInitializer init = new ConfigPartitionInitializer( instanceLayout, dnFactory, schemaManager );
        LdifPartition configurationPartition = init.initConfigPartition();

        return readConfiguration( configurationPartition );
    }


    /**
     * Reads the configuration from the given partition.
     *
     * @param partition the configuration partition
     * @return the associated configuration bean
     * @throws LdapException if we weren't able to load the configuration
     */
    private static Configuration readConfiguration( AbstractBTreePartition partition ) throws LdapException
    {
        if ( partition != null )
        {
            ConfigPartitionReader cpReader = new ConfigPartitionReader( partition );
            ConfigBean configBean = cpReader.readConfig();
            return new Configuration( configBean, partition );
        }

        return null;
    }


    /**
     * Reads the configuration from the given connection.
     *
     * @param input the editor input
     * @param monitor the studio progress monitor
     * @return the associated configuration bean
     * @throws Exception if we weren't able to load the configuration
     */
    private Configuration readConfiguration( ConnectionServerConfigurationInput input, StudioProgressMonitor monitor ) throws Exception
    {
        if ( input != null )
        {
            SchemaManager schemaManager = ApacheDS2ConfigurationPlugin.getDefault().getSchemaManager();

            // Getting the browser connection associated with the connection in the input
            IBrowserConnection browserConnection = BrowserCorePlugin.getDefault().getConnectionManager()
                .getBrowserConnection( input.getConnection() );

            // Creating and initializing the configuration partition
            EntryBasedConfigurationPartition configurationPartition = new EntryBasedConfigurationPartition(
                schemaManager );
            configurationPartition.initialize();

            // Opening the connection
            openConnection( input, monitor );

            // Creating the search parameter
            SearchParameter configSearchParameter = new SearchParameter();
            configSearchParameter.setSearchBase( new Dn( ServerDNConstants.CONFIG_DN ) ); //$NON-NLS-1$
            //configSearchParameter.setSearchBase( new Dn( "ou=config" ) ); //$NON-NLS-1$
            configSearchParameter.setFilter( LdapConstants.OBJECT_CLASS_STAR ); //$NON-NLS-1$
            configSearchParameter.setScope( SearchScope.OBJECT );
            configSearchParameter.setReturningAttributes( SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );

            // Looking for the 'ou=config' base entry
            Entry configEntry = null;
            StudioSearchResultEnumeration enumeration = SearchRunnable.search( browserConnection, configSearchParameter,
                monitor );

            // Checking if an error occurred
            if ( monitor.errorsReported() )
            {
                throw monitor.getException();
            }

            // Getting the entry
            if ( enumeration.hasMore() )
            {
                // Creating the 'ou=config' base entry
                StudioSearchResult searchResult = enumeration.next();
                configEntry = new DefaultEntry( schemaManager, searchResult.getEntry() );
            }

            enumeration.close();

            // Verifying we found the 'ou=config' base entry
            if ( configEntry == null )
            {
                ApacheDS2ConfigurationPlugin.getDefault().getLog().log( 
                    new Status( Status.ERROR, "org.apache.directory.studio.apacheds.configuration", 
                        Messages.getString( "LoadConfigurationRunnable.UnableToFindConfigBaseEntry" ) ) );
                throw new LdapNoSuchObjectException(
                    Messages.getString( "LoadConfigurationRunnable.UnableToFindConfigBaseEntry" ) ); //$NON-NLS-1$
            }

            // Creating a list to hold the entries that need to be checked
            // for children and added to the partition
            List<Entry> entries = new ArrayList<Entry>();
            entries.add( configEntry );

            // Looping on the entries list until it's empty
            while ( !entries.isEmpty() )
            {
                // Removing the first entry from the list
                Entry entry = entries.remove( 0 );

                // Adding the entry to the partition
                configurationPartition.addEntry( entry );

                SearchParameter searchParameter = new SearchParameter();
                searchParameter.setSearchBase( entry.getDn() );
                searchParameter.setFilter( LdapConstants.OBJECT_CLASS_STAR ); //$NON-NLS-1$
                searchParameter.setScope( SearchScope.ONELEVEL );
                searchParameter.setReturningAttributes( SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );

                // Looking for the children of the entry
                StudioSearchResultEnumeration childrenEnumeration = SearchRunnable.search( browserConnection,
                    searchParameter, monitor );

                // Checking if an error occurred
                if ( monitor.errorsReported() )
                {
                    throw monitor.getException();
                }

                while ( childrenEnumeration.hasMore() )
                {
                    // Adding the children to the list of entries
                    StudioSearchResult searchResult = childrenEnumeration.next();
                    entries.add( new DefaultEntry( schemaManager, searchResult.getEntry() ) );
                }
                
                childrenEnumeration.close();
            }

            // Setting the created partition to the input
            input.setOriginalPartition( configurationPartition );

            return readConfiguration( configurationPartition );
        }

        return null;
    }


    /**
     * Opens the connection.
     *
     * @param input the input
     * @param monitor the monitor
     */
    private void openConnection( ConnectionServerConfigurationInput input, StudioProgressMonitor monitor )
    {
        Connection connection = input.getConnection();

        if ( connection != null && !connection.getConnectionWrapper().isConnected() )
        {
            connection.getConnectionWrapper().connect( monitor );
            
            if ( connection.getConnectionWrapper().isConnected() )
            {
                connection.getConnectionWrapper().bind( monitor );
            }

            if ( connection.getConnectionWrapper().isConnected() )
            {
                for ( IConnectionListener listener : ConnectionCorePlugin.getDefault()
                    .getConnectionListeners() )
                {
                    listener.connectionOpened( connection, monitor );
                }
                
                ConnectionEventRegistry.fireConnectionOpened( connection, input );
            }
        }
    }
}
