/**
 * 
 */


package gov.va.med.cds.log4j;


import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.Hashtable;
import java.util.Properties;

import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;

import org.apache.logging.log4j.core.ErrorHandler;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.DefaultErrorHandler;
import org.apache.logging.log4j.status.StatusLogger;


/**
 * @author DNS   egberb
 * 
 */
public class JmsQueueAppender implements AppenderInterface//extends AppenderSkeleton
{

    String securityPrincipalName;
    String securityCredentials;
    String initialContextFactoryName;
    String urlPkgPrefixes;
    String providerURL;
    String queueBindingName;
    String qcfBindingName;
    String userName;
    String password;
    boolean locationInfo;

    QueueConnection queueConnection;
    QueueSession queueSession;
    QueueSender queueSender;
    
    ErrorHandler errorHandler;
    
    //where/when does these get set
    String name = "CONFUSED";
    boolean closed = false;


    public JmsQueueAppender( )
    {
    	errorHandler = new DefaultErrorHandler(this);
    }


    /**
     * The <b>QueueConnectionFactoryBindingName</b> option takes a string value. Its value will be used to lookup the
     * appropriate <code>QueueConnectionFactory</code> from the JNDI context.
     */
    public void setQueueConnectionFactoryBindingName( String qcfBindingName )
    {
        this.qcfBindingName = qcfBindingName;
    }


    /**
     * Returns the value of the <b>QueueConnectionFactoryBindingName</b> option.
     */
    public String getQueueConnectionFactoryBindingName( )
    {
        return qcfBindingName;
    }


    /**
     * The <b>QueueBindingName</b> option takes a string value. Its value will be used to lookup the appropriate
     * <code>Queue</code> from the JNDI context.
     */
    public void setQueueBindingName( String queueBindingName )
    {
        this.queueBindingName = queueBindingName;
    }


    /**
     * Returns the value of the <b>QueueBindingName</b> option.
     */
    public String getQueueBindingName( )
    {
        return queueBindingName;
    }


    /**
     * Returns value of the <b>LocationInfo</b> property which determines whether location (stack) info is sent to the
     * remote subscriber.
     */
    public boolean getLocationInfo( )
    {
        return locationInfo;
    }


    /**
     * Options are activated and become effective only after calling this method.
     */
    public void activateOptions( )
    {
        QueueConnectionFactory queueConnectionFactory;

        try
        {
            Context jndi;
            //LogLog.debug( "Getting initial context." );
            StatusLogger.getLogger().debug("Getting initial context.");
            if ( initialContextFactoryName != null )
            {
                Properties env = new Properties();
                env.put( Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName );
                if ( providerURL != null )
                {
                    env.put( Context.PROVIDER_URL, providerURL );
                }
                else
                {
                    //LogLog.warn( "You have set InitialContextFactoryName option but not the " + "ProviderURL. This is likely to cause problems." );
                    StatusLogger.getLogger().warn( "You have set InitialContextFactoryName option but not the " + "ProviderURL. This is likely to cause problems." );
                }
                if ( urlPkgPrefixes != null )
                {
                    env.put( Context.URL_PKG_PREFIXES, urlPkgPrefixes );
                }

                if ( securityPrincipalName != null )
                {
                    env.put( Context.SECURITY_PRINCIPAL, securityPrincipalName );
                    if ( securityCredentials != null )
                    {
                        env.put( Context.SECURITY_CREDENTIALS, securityCredentials );
                    }
                    else
                    {
                        //LogLog.warn( "You have set SecurityPrincipalName option but not the SecurityCredentials. This is likely to cause problems." );
                        StatusLogger.getLogger().warn( "You have set SecurityPrincipalName option but not the "
                                + "SecurityCredentials. This is likely to cause problems." );
                    }
                }
                jndi = new InitialContext( env );
            }
            else
            {
                jndi = new InitialContext();
            }

            //LogLog.debug( "Looking up [" + qcfBindingName + "]" );
            StatusLogger.getLogger().debug( "Looking up [" + qcfBindingName + "]" );
            queueConnectionFactory = ( QueueConnectionFactory )lookup( jndi, qcfBindingName );
            //LogLog.debug( "About to create QueueConnection." );
            StatusLogger.getLogger().debug( "About to create QueueConnection." );
            if ( userName != null )
            {
                queueConnection = queueConnectionFactory.createQueueConnection( userName, password );
            }
            else
            {
                queueConnection = queueConnectionFactory.createQueueConnection();
            }

            StatusLogger.getLogger().debug( "Creating QueueSession, non-transactional, " + "in AUTO_ACKNOWLEDGE mode." );
            queueSession = queueConnection.createQueueSession( false, Session.AUTO_ACKNOWLEDGE );

            StatusLogger.getLogger().debug( "Looking up queue name [" + queueBindingName + "]." );
            Queue queue = ( Queue )lookup( jndi, queueBindingName );

            StatusLogger.getLogger().debug( "Creating QueuePublisher." );
            queueSender = queueSession.createSender( queue );

            StatusLogger.getLogger().debug( "Starting QueueConnection." );
            queueConnection.start();

            jndi.close();
        }
        catch ( Exception e )
        {
        	errorHandler.error( "Error while activating options for appender named [" + name + "].", e);
        }
    }


    protected Object lookup( Context ctx, String name )
        throws NamingException
    {
        try
        {
            return ctx.lookup( name );
        }
        catch ( NameNotFoundException e )
        {
        	StatusLogger.getLogger().error( "Could not find name [" + name + "]." );
            throw e;
        }
    }


    protected boolean checkEntryConditions( )
    {
        String fail = null;

        if ( this.queueConnection == null )
        {
            fail = "No QueueConnection";
        }
        else if ( this.queueSession == null )
        {
            fail = "No QueueSession";
        }
        else if ( this.queueSender == null )
        {
            fail = "No QueuePublisher";
        }

        if ( fail != null )
        {
            errorHandler.error( fail + " for JMSAppender named [" + name + "]." );
            return false;
        }
        else
        {
            return true;
        }
    }


    /**
     * Close this JMSAppender. Closing releases all resources used by the appender. A closed appender cannot be
     * re-opened.
     */
    public synchronized void close( )
    {
        // The synchronized modifier avoids concurrent append and close operations

        if ( this.closed )
            return;

        StatusLogger.getLogger().debug( "Closing appender [" + name + "]." );
        this.closed = true;

        try
        {
            if ( queueSession != null )
                queueSession.close();
            if ( queueConnection != null )
                queueConnection.close();
        }
        catch ( Exception e )
        {
        	StatusLogger.getLogger().error( "Error while closing JMSAppender [" + name + "].", e );
        }
        // Help garbage collection
        queueSender = null;
        queueSession = null;
        queueConnection = null;
    }


    /**
     * This method was called by {@link AppenderSkeleton#doAppend} method to do most of the real appending work in log4j2 it is called by?.
     */
    @Override
    public void append( LogEvent event )
    {
        if ( !checkEntryConditions() )
        {
            return;
        }

        try
        {
            ObjectMessage msg = queueSession.createObjectMessage();
            if ( locationInfo )
            {
                //event.getLocationInformation();
            	event.setIncludeLocation(locationInfo);
            }
            msg.setObject( event );
            queueSender.send( msg );
        }
        catch ( Exception e )
        {
            String applicationName = null;
            try
            {
                ApplicationLogEvent appLogEvent = new ApplicationLogEvent( event );
                applicationName = appLogEvent.getAppName();
            }
            catch ( Exception ex )
            {
                applicationName = "Application name not found";
                //errorHandler.error( applicationName, ex, ErrorCode.GENERIC_FAILURE );
                errorHandler.error( applicationName, ex );
            }
            
            StringWriter errors = new StringWriter();
            e.printStackTrace(new PrintWriter(errors));
            String stackTrace = errors.toString();
            errorHandler.error( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, null, applicationName, "Could not publish  message in JMSAppender [" + name + "]." ));
        }
    }


    /**
     * Returns the value of the <b>InitialContextFactoryName</b> option. See {@link #setInitialContextFactoryName} for
     * more details on the meaning of this option.
     */
    public String getInitialContextFactoryName( )
    {
        return initialContextFactoryName;
    }


    /**
     * Setting the <b>InitialContextFactoryName</b> method will cause this <code>JMSAppender</code> instance to use
     * the {@link InitialContext#InitialContext(Hashtable)} method instead of the no-argument constructor. If you set
     * this option, you should also at least set the <b>ProviderURL</b> option.
     * 
     * <p>
     * See also {@link #setProviderURL(String)}.
     */
    public void setInitialContextFactoryName( String initialContextFactoryName )
    {
        this.initialContextFactoryName = initialContextFactoryName;
    }


    public String getProviderURL( )
    {
        return providerURL;
    }


    public void setProviderURL( String providerURL )
    {
        this.providerURL = providerURL;
    }


    String getURLPkgPrefixes( )
    {
        return urlPkgPrefixes;
    }


    public void setURLPkgPrefixes( String urlPkgPrefixes )
    {
        this.urlPkgPrefixes = urlPkgPrefixes;
    }


    public String getSecurityCredentials( )
    {
        return securityCredentials;
    }


    public void setSecurityCredentials( String securityCredentials )
    {
        this.securityCredentials = securityCredentials;
    }


    public String getSecurityPrincipalName( )
    {
        return securityPrincipalName;
    }


    public void setSecurityPrincipalName( String securityPrincipalName )
    {
        this.securityPrincipalName = securityPrincipalName;
    }


    public String getUserName( )
    {
        return userName;
    }


    /**
     * The user name to use when {@link QueueConnectionFactory#createQueueConnection(String, String) creating a queue
     * session}. If you set this option, you should also set the <b>Password</b> option. See {@link
     * #setPassword(String)}.
     */
    public void setUserName( String userName )
    {
        this.userName = userName;
    }


    public String getPassword( )
    {
        return password;
    }


    /**
     * The paswword to use when creating a queue session.
     */
    public void setPassword( String password )
    {
        this.password = password;
    }


    /**
     * If true, the information sent to the remote subscriber will include caller's location information. By default no
     * location information is sent to the subscriber.
     */
    public void setLocationInfo( boolean locationInfo )
    {
        this.locationInfo = locationInfo;
    }


    /**
     * Returns the QueueConnection used for this appender. Only valid after activateOptions() method has been invoked.
     */
    protected QueueConnection getQueueConnection( )
    {
        return queueConnection;
    }


    /**
     * Returns the QueueSession used for this appender. Only valid after activateOptions() method has been invoked.
     */
    protected QueueSession getQueueSession( )
    {
        return queueSession;
    }


    /**
     * Returns the QueuePublisher used for this appender. Only valid after activateOptions() method has been invoked.
     */
    protected QueueSender getQueueSender( )
    {
        return queueSender;
    }


    /**
     * The JMSAppender sends serialized events and consequently does not require a layout.
     */
    public boolean requiresLayout( )
    {
        return false;
    }


	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public Layout<? extends Serializable> getLayout() {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public boolean ignoreExceptions() {
		// TODO Auto-generated method stub
		return false;
	}


	@Override
	public ErrorHandler getHandler() {
		return errorHandler;
	}


	@Override
	public void setHandler(ErrorHandler paramErrorHandler) {
		this.errorHandler = paramErrorHandler;
	}


	@Override
	public State getState() {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public void initialize() {
		// TODO Auto-generated method stub
		activateOptions();
		
	}


	@Override
	public boolean isStarted() {
		// TODO Auto-generated method stub
		return checkEntryConditions();
	}


	@Override
	public boolean isStopped() {
		// TODO Auto-generated method stub
		return false;
	}


	@Override
	public void start() {
		// TODO Auto-generated method stub
		
	}


	@Override
	public void stop() {
		// TODO Auto-generated method stub
		try{
		 if ( queueSession != null )
             queueSession.close();
         if ( queueConnection != null )
             queueConnection.close();
		}catch(Exception e){
			//ignore - not dependent upon this stop method
		}
	}

}
