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


import java.util.Hashtable;

import javax.naming.directory.SearchControls;
import javax.naming.event.EventContext;
import javax.naming.event.NamingExceptionEvent;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.UnsolicitedNotification;
import javax.naming.ldap.UnsolicitedNotificationEvent;
import javax.naming.ldap.UnsolicitedNotificationListener;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.directory.daemon.AvailablePortFinder;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.asn1.codec.DecoderException;
import org.apache.directory.shared.ldap.message.extended.GracefulDisconnect;
import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;


/**
 * Responds to unsolicited notifications by launching an external process.  Also 
 * reconnects to the server an launches another process to notify that the server
 * is back up.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 * @version $Rev: 434420 $
 */
public class DisconnectNotificationCommand extends ToolCommand implements UnsolicitedNotificationListener
{
    UnsolicitedNotification notification;
    boolean canceled = false;
    private String host = "localhost";
    private int port = 10389;
    private String bindDn = "uid=admin,ou=system";
    private String password = "secret";


    //    private String shutdownCommand = "echo"; 
    //    private String[] shutdownCommandArgs = new String[] { 
    //        "server $HOST:$PORT will shutdown for $OFFLINE minutes in $DELAY seconds" };

    protected DisconnectNotificationCommand()
    {
        super( "notifications" );
    }


    public void notificationReceived( UnsolicitedNotificationEvent evt )
    {
        notification = evt.getNotification();

        if ( notification.getID().equals( NoticeOfDisconnect.EXTENSION_OID ) )
        {
            System.out.println( "\nRecieved NoticeOfDisconnect: " + NoticeOfDisconnect.EXTENSION_OID );
            System.out.println( "Expect to loose this connection without further information." );
            canceled = true;
        }
        else if ( notification.getID().equals( GracefulDisconnect.EXTENSION_OID ) )
        {
            System.out.println( "Recieved GracefulDisconnect: " + GracefulDisconnect.EXTENSION_OID );
            GracefulDisconnect gd = null;

            try
            {
                gd = new GracefulDisconnect( notification.getEncodedValue() );
            }
            catch ( DecoderException de )
            {
                // TODO Auto-generated catch block
                de.printStackTrace();
            }

            System.out.println( "LDAP server will shutdown in " + gd.getDelay() + " seconds." );
            System.out.println( "LDAP server will be back online in " + gd.getTimeOffline() + " minutes." );

            if ( gd.getDelay() > 0 )
            {
                Thread t = new Thread( new Counter( gd.getDelay() ) );
                t.start();
            }
        }
        else
        {
            System.out.println( "Unknown event recieved with OID: " + evt.getNotification().getID() );
        }
    }


    public void namingExceptionThrown( NamingExceptionEvent evt )
    {
        canceled = true;
        System.out.println( "Got an excption event: " + evt.getException().getLocalizedMessage() );
        System.out.println( "Process shutting down abruptly." );
        System.exit( 1 );
    }

    class Counter implements Runnable
    {
        int delay;


        Counter( int delay )
        {
            this.delay = delay;
        }


        public void run()
        {
            System.out.println( "Starting countdown until server shutdown:" );
            System.out.print( "[" );
            long delayMillis = delay * 1000 - 1000; // 1000 is for setup costs
            long startTime = System.currentTimeMillis();
            while ( System.currentTimeMillis() - startTime < delayMillis && !canceled )
            {
                try
                {
                    Thread.sleep( 1000 );
                }
                catch ( InterruptedException e )
                {
                }
                System.out.print( "." );
            }

            if ( canceled )
            {
                System.out.println( " -- countdown canceled -- " );
            }
            else
            {
                System.out.println( "]" );
                System.out.println( "Client shutting down gracefully." );
                System.exit( 0 );
            }
        }
    }


    public void execute( CommandLine cmd ) throws Exception
    {
        processOptions( cmd );

        Hashtable env = new Hashtable();
        env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
        env.put( "java.naming.provider.url", "ldap://" + host + ":" + port );
        env.put( "java.naming.security.principal", bindDn );
        env.put( "java.naming.security.credentials", password );
        env.put( "java.naming.security.authentication", "simple" );

        LdapContext ctx = new InitialLdapContext( env, null );
        ctx = ctx.newInstance( null );
        UnsolicitedNotificationListener listener = new DisconnectNotificationCommand();
        ( ( EventContext ) ctx ).addNamingListener( "", SearchControls.SUBTREE_SCOPE, listener );

        System.out.println( "Listening for notifications." );
        System.out.println( "Press any key to terminate." );
        System.in.read();
        ctx.close();
        System.out.println( "Process terminated!!!" );
    }


    private void processOptions( CommandLine cmd )
    {
        if ( isDebugEnabled() )
        {
            System.out.println( "Processing options for disconnect notifications ..." );
        }

        // -------------------------------------------------------------------
        // figure out and error check the port value
        // -------------------------------------------------------------------

        if ( cmd.hasOption( 'p' ) ) // - user provided port w/ -p takes precedence
        {
            String val = cmd.getOptionValue( 'p' );
            try
            {
                port = Integer.parseInt( val );
            }
            catch ( NumberFormatException e )
            {
                System.err.println( I18n.err( I18n.ERR_193, val ) );
                System.exit( 1 );
            }

            if ( port > AvailablePortFinder.MAX_PORT_NUMBER )
            {
                System.err.println( I18n.err( I18n.ERR_194, val, AvailablePortFinder.MAX_PORT_NUMBER ) );
                System.exit( 1 );
            }
            else if ( port < AvailablePortFinder.MIN_PORT_NUMBER )
            {
                System.err.println( I18n.err( I18n.ERR_195, val, AvailablePortFinder.MIN_PORT_NUMBER ) );
                System.exit( 1 );
            }

            if ( isDebugEnabled() )
            {
                System.out.println( "port overriden by -p option: " + port );
            }
        }
        else if ( getApacheDS() != null )
        {
            port = getApacheDS().getLdapServer().getPort();

            if ( isDebugEnabled() )
            {
                System.out.println( "port overriden by server.xml configuration: " + port );
            }
        }
        else if ( isDebugEnabled() )
        {
            System.out.println( "port set to default: " + port );
        }

        // -------------------------------------------------------------------
        // figure out the host value
        // -------------------------------------------------------------------

        if ( cmd.hasOption( 'h' ) )
        {
            host = cmd.getOptionValue( 'h' );

            if ( isDebugEnabled() )
            {
                System.out.println( "host overriden by -h option: " + host );
            }
        }
        else if ( isDebugEnabled() )
        {
            System.out.println( "host set to default: " + host );
        }

        // -------------------------------------------------------------------
        // figure out the password value
        // -------------------------------------------------------------------

        if ( cmd.hasOption( 'w' ) )
        {
            password = cmd.getOptionValue( 'w' );

            if ( isDebugEnabled() )
            {
                System.out.println( "password overriden by -w option: " + password );
            }
        }
        else if ( isDebugEnabled() )
        {
            System.out.println( "password set to default: " + password );
        }

        // -------------------------------------------------------------------
        // figure out the binddn value
        // -------------------------------------------------------------------

        if ( cmd.hasOption( 'u' ) )
        {
            bindDn = cmd.getOptionValue( 'u' );

            if ( isDebugEnabled() )
            {
                System.out.println( "binddn overriden by -u option: " + bindDn );
            }
        }
        else if ( isDebugEnabled() )
        {
            System.out.println( "binddn set to default: " + bindDn );
        }
    }


    public Options getOptions()
    {
        Options opts = new Options();
        Option op = new Option( "h", "host", true, "server host: defaults to localhost" );
        op.setRequired( false );
        opts.addOption( op );
        op = new Option( "p", "port", true, "server port: defaults to 10389 or server.xml specified port" );
        op.setRequired( false );
        opts.addOption( op );
        op = new Option( "w", "password", true, "the apacheds administrator's password: defaults to secret" );
        op.setRequired( false );
        opts.addOption( op );
        op = new Option( "u", "binddn", true, "an apacheds user's dn: defaults to " + bindDn );
        op.setRequired( false );
        opts.addOption( op );
        op = new Option( "i", "install-path", true, "path to apacheds installation directory" );
        op.setRequired( false );
        opts.addOption( op );
        return opts;
    }
}
