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


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.directory.api.util.FileUtils;
import org.apache.directory.studio.connection.core.event.ConnectionEventRegistry;
import org.apache.directory.studio.connection.core.event.ConnectionUpdateListener;
import org.apache.directory.studio.connection.core.io.ConnectionIO;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;


/**
 * This class is used to manage {@link ConnectionFolder}s.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class ConnectionFolderManager implements ConnectionUpdateListener
{
    private static final String CONNECTION_FOLDERS_FILENAME = "connectionFolders.xml"; //$NON-NLS-1$

    private static final String ROOT_ID = "0"; //$NON-NLS-1$

    /** The root connection folder. */
    private ConnectionFolder root;

    /** The list of folders. */
    private Set<ConnectionFolder> folderList;


    /**
     * Creates a new instance of ConnectionFolderManager.
     */
    public ConnectionFolderManager()
    {
        this.root = new ConnectionFolder( "" ); //$NON-NLS-1$s
        this.root.setId( ROOT_ID ); //$NON-NLS-1$s
        this.folderList = new HashSet<ConnectionFolder>();

        loadConnectionFolders();
        ConnectionEventRegistry.addConnectionUpdateListener( this, ConnectionCorePlugin.getDefault().getEventRunner() );

        this.folderList.add( this.root );
    }


    /**
     * Gets the filename of the connection folder store.
     *
     * @return
     *      the filename of the connection folder store
     */
    public static final String getConnectionFolderStoreFileName()
    {
        String filename = ConnectionCorePlugin.getDefault().getStateLocation().append( CONNECTION_FOLDERS_FILENAME )
            .toOSString();
        return filename;
    }


    /**
     * Adds the ConnectionFolder to the connection folder list. If there is already a
     * connection folder with the same name the new connection folder is renamed.
     * 
     * @param connectionFolder the connection folder
     */
    public void addConnectionFolder( ConnectionFolder connectionFolder )
    {
        if ( getConnectionFolderByName( connectionFolder.getName() ) != null )
        {
            String newConnectionFolderName = Messages.bind( Messages.copy_n_of_s, "", connectionFolder.getName() ); //$NON-NLS-1$
            for ( int i = 2; getConnectionFolderByName( newConnectionFolderName ) != null; i++ )
            {
                newConnectionFolderName = Messages.bind( Messages.copy_n_of_s, i + " ", connectionFolder.getName() ); //$NON-NLS-1$
            }
            connectionFolder.setName( newConnectionFolderName );
        }

        folderList.add( connectionFolder );
        ConnectionEventRegistry.fireConnectonFolderAdded( connectionFolder, this );
    }


    /**
     * Removes the given ConnectionFolder from the connection folder list.
     *
     * @param connectionFolder
     *      the connection folder to remove
     */
    public void removeConnectionFolder( ConnectionFolder connectionFolder )
    {
        folderList.remove( connectionFolder );
        ConnectionEventRegistry.fireConnectonFolderRemoved( connectionFolder, this );
    }


    /**
     * Gets an array of connection folders.
     *
     * @return
     *      an array of connection folders
     */
    public ConnectionFolder[] getConnectionFolders()
    {
        return folderList.toArray( new ConnectionFolder[0] );
    }


    /**
     * Gets a connection folder from its id.
     *
     * @param id
     *      the id of the connection folder
     * @return
     *      the corresponding connection folder
     */
    public ConnectionFolder getConnectionFolderById( String id )
    {
        for ( ConnectionFolder folder : folderList )
        {
            if ( folder.getId().equals( id ) )
            {
                return folder;
            }
        }
        return null;
    }


    /**
     * Gets a connection folder from its name.
     *
     * @param name
     *      the name of the connection folder
     * @return
     *      the corresponding connection folder
     */
    public ConnectionFolder getConnectionFolderByName( String name )
    {
        for ( ConnectionFolder folder : folderList )
        {
            if ( folder.getName().equals( name ) )
            {
                return folder;
            }
        }
        return null;
    }


    /**
     * Gets the parent connection folder of the given connection.
     *
     * @param connection
     *      the connection used to search the parent
     * @return
     *      the parent connection folder of the given connection
     */
    public ConnectionFolder getParentConnectionFolder( Connection connection )
    {
        for ( ConnectionFolder folder : folderList )
        {
            if ( folder.getConnectionIds().contains( connection.getId() ) )
            {
                return folder;
            }
        }
        return getRootConnectionFolder();
    }


    /**
     * Gets the parent connection folder of the given connection folder 
     * or null if the folder doesn't have a parent folder.
     *
     * @param connectionFolder
     *      the connection folder used to search the parent
     * @return
     *      the parent connection folder of the given connection folder
     *      or null if the folder doesn't have a parent folder
     */
    public ConnectionFolder getParentConnectionFolder( ConnectionFolder connectionFolder )
    {
        for ( ConnectionFolder folder : folderList )
        {
            if ( folder.getSubFolderIds().contains( connectionFolder.getId() ) )
            {
                return folder;
            }
        }
        return null;
    }


    /**
     * Gets the all sub-folders of the given folder including the given folder.
     * 
     * @param folder the folder
     * 
     * @return all sub-folders
     */
    public Set<ConnectionFolder> getAllSubFolders( ConnectionFolder folder )
    {
        Set<ConnectionFolder> allSubFolders = new HashSet<ConnectionFolder>();

        List<String> ids = new ArrayList<String>();
        ids.add( folder.getId() );
        while ( !ids.isEmpty() )
        {
            String id = ids.remove( 0 );
            ConnectionFolder subFolder = getConnectionFolderById( id );
            allSubFolders.add( subFolder );

            ids.addAll( subFolder.getSubFolderIds() );
        }

        return allSubFolders;
    }


    /**
     * Gets the all parent folders of the given folder including the given folder.
     * 
     * @param folder the folder
     * 
     * @return all parent folders
     */
    public Set<ConnectionFolder> getAllParentFolders( ConnectionFolder folder )
    {
        Set<ConnectionFolder> allParentFolders = new HashSet<ConnectionFolder>();

        do
        {
            allParentFolders.add( folder );
            folder = getParentConnectionFolder( folder );
        }
        while ( folder != null );

        return allParentFolders;
    }


    /**
     * Gets the root connection folder.
     * 
     * @return the root connection folder
     */
    public ConnectionFolder getRootConnectionFolder()
    {
        return root;
    }


    /**
     * Sets the root connection folder.
     * 
     * @param root the new root connection folder
     */
    public void setRootConnectionFolder( ConnectionFolder root )
    {
        this.root = root;
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionAdded(org.apache.directory.studio.connection.core.Connection)
     */
    public void connectionAdded( Connection connection )
    {
        saveConnectionFolders();
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionRemoved(org.apache.directory.studio.connection.core.Connection)
     */
    public void connectionRemoved( Connection connection )
    {
        saveConnectionFolders();
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionUpdated(org.apache.directory.studio.connection.core.Connection)
     */
    public void connectionUpdated( Connection connection )
    {
        saveConnectionFolders();
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionOpened(org.apache.directory.studio.connection.core.Connection)
     */
    public void connectionOpened( Connection connection )
    {
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionClosed(org.apache.directory.studio.connection.core.Connection)
     */
    public void connectionClosed( Connection connection )
    {
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionFolderModified(org.apache.directory.studio.connection.core.ConnectionFolder)
     */
    public void connectionFolderModified( ConnectionFolder connectionFolder )
    {
        saveConnectionFolders();
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionFolderAdded(org.apache.directory.studio.connection.core.ConnectionFolder)
     */
    public void connectionFolderAdded( ConnectionFolder connectionFolder )
    {
        saveConnectionFolders();
    }


    /**
     * @see org.apache.directory.studio.connection.core.event.ConnectionUpdateListener#connectionFolderRemoved(org.apache.directory.studio.connection.core.ConnectionFolder)
     */
    public void connectionFolderRemoved( ConnectionFolder connectionFolder )
    {
        saveConnectionFolders();
    }


    /**
     * Saves the Connection Folders
     */
    private synchronized void saveConnectionFolders()
    {
        File file = new File( getConnectionFolderStoreFileName() );
        File tempFile = new File( getConnectionFolderStoreFileName() + ConnectionManager.TEMP_SUFFIX );

        // To avoid a corrupt file, save object to a temp file first 
        try ( FileOutputStream fileOutputStream = new FileOutputStream( tempFile ) )
        {
            ConnectionIO.saveConnectionFolders( folderList, fileOutputStream );
        }
        catch ( IOException e )
        {
            Status status = new Status( IStatus.ERROR, ConnectionCoreConstants.PLUGIN_ID,
                Messages.error__saving_connections + e.getMessage(), e );
            ConnectionCorePlugin.getDefault().getLog().log( status );
            return;
        }

        // move temp file to good file
        if ( file.exists() )
        {
            file.delete();
        }

        try
        {
            String content = FileUtils.readFileToString( tempFile, ConnectionManager.ENCODING_UTF8 );
            FileUtils.writeStringToFile( file, content, ConnectionManager.ENCODING_UTF8 );
        }
        catch ( IOException e )
        {
            Status status = new Status( IStatus.ERROR, ConnectionCoreConstants.PLUGIN_ID,
                Messages.error__saving_connections + e.getMessage(), e );
            ConnectionCorePlugin.getDefault().getLog().log( status );
            return;
        }
    }


    /**
     * Loads the Connection Folders
     */
    private synchronized void loadConnectionFolders()
    {
        ConnectionEventRegistry.suspendEventFiringInCurrentThread();

        File file = new File( getConnectionFolderStoreFileName() );
        if ( file.exists() )
        {
            try ( FileInputStream fileInputStream = new FileInputStream( file ) )
            {
                folderList = ConnectionIO.loadConnectionFolders( fileInputStream );
            }
            catch ( Exception e )
            {
                Status status = new Status( IStatus.ERROR, ConnectionCoreConstants.PLUGIN_ID,
                    Messages.error__loading_connections + e.getMessage(), e );
                ConnectionCorePlugin.getDefault().getLog().log( status );
            }
        }

        if ( !folderList.isEmpty() )
        {
            for ( ConnectionFolder folder : folderList )
            {
                if ( ROOT_ID.equals( folder.getId() ) )
                {
                    root = folder;
                }
            }
        }
        else
        {
            Connection[] connections = ConnectionCorePlugin.getDefault().getConnectionManager().getConnections();
            for ( Connection connection : connections )
            {
                root.addConnectionId( connection.getId() );
            }
        }

        ConnectionEventRegistry.resumeEventFiringInCurrentThread();
    }

}
