using System;
using System.Threading;
using log4net;
using Apache.Qpid.Messaging;
using Apache.Qpid.Client.Qms;

namespace Apache.Qpid.Client.Tests.interop
{
    public class TopicPublisher
    {
        private static ILog log = LogManager.GetLogger(typeof(TopicPublisher));

        /// <summary> The default AMQ connection URL to use for tests. </summary>
        const string DEFAULT_URI = "amqp://guest:guest@default/test?brokerlist='tcp://localhost:5672'";

        /// <summary> Holds the default test timeout for broker communications before tests give up. </summary>
        const int TIMEOUT = 10000;

        /// <summary> Holds the routing key for the topic to receive test messages on. </summary>
        const string CONTROL_ROUTING_KEY = "topic_control";

        /// <summary> Holds the routing key for the queue to send reports to. </summary>
        const string RESPONSE_ROUTING_KEY = "response";

        /// <summary> Holds the number of messages to send in each test run. </summary>
        private int numMessages;

        /// <summary> Holds the number of subscribers listening to the messsages. </summary>
        private int numSubscribers;

        /// <summary> A monitor used to wait for all reports to arrive back from consumers on. </summary>
        private AutoResetEvent allReportsReceivedEvt = new AutoResetEvent(false);

        /// <summary> Holds the connection to listen on. </summary>
        private IConnection connection;

        /// <summary> Holds the channel for all test messages.</summary>
        private IChannel channel;

        /// <summary> Holds the producer to send test messages on. </summary>
        private IMessagePublisher publisher;

        /// <summary>
        /// Creates a topic publisher that will send the specifed number of messages and expect the specifed number of report back from test
        /// subscribers.
        /// </summary>
        /// 
        /// <param name="connectionUri">The broker URL.</param>
        /// <param name="numMessages">The number of messages to send in each test.</param>
        /// <param name="numSubscribers">The number of subscribes that are expected to reply with a report.</param>
        TopicPublisher(string connectionUri, int numMessages, int numSubscribers)
        {
            log.Debug("TopicPublisher(string connectionUri = " + connectionUri + ", int numMessages = "+ numMessages + 
                      ", int numSubscribers = " + numSubscribers + "): called");

            // Create a connection to the broker.
            IConnectionInfo connectionInfo = QpidConnectionInfo.FromUrl(connectionUri);
            connection = new AMQConnection(connectionInfo);

            // Establish a session on the broker.
            channel = connection.CreateChannel(false, AcknowledgeMode.AutoAcknowledge, 1);

            // Set up a queue to listen for reports on.
            string responseQueueName = channel.GenerateUniqueName();
            channel.DeclareQueue(responseQueueName, false, true, true);
            
            // Set this listener up to listen for reports on the response queue.
            channel.Bind(responseQueueName, ExchangeNameDefaults.DIRECT, RESPONSE_ROUTING_KEY);
            //channel.Bind(responseQueueName, "<<default>>", RESPONSE_ROUTING_KEY);
            IMessageConsumer consumer = channel.CreateConsumerBuilder(responseQueueName)               
                .Create();
            consumer.OnMessage += new MessageReceivedDelegate(OnMessage);

            // Set up this listener with a producer to send the test messages and report requests on.
            publisher = channel.CreatePublisherBuilder()
                .WithExchangeName(ExchangeNameDefaults.TOPIC)
                .WithRoutingKey(CONTROL_ROUTING_KEY)
                .Create();

            // Keep the test parameters.
            this.numMessages = numMessages;
            this.numSubscribers = numSubscribers;

            connection.Start();
            Console.WriteLine("Sending messages and waiting for reports...");
        }

        /// <summary>
        /// Start a test subscriber. The broker URL must be specified as the first command line argument.
        /// </summary>
        /// 
        /// <param name="argv">The command line arguments, broker URL first.</param>
        public static void Main(String[] argv)
        {
            // Create an instance of this publisher with the command line parameters.
            TopicPublisher publisher = new TopicPublisher(DEFAULT_URI, 1, 1);

            // Publish the test messages.
            publisher.DoTest();
        }

        /// <summary>
        /// Sends the test messages and waits for all subscribers to reply with a report.
        /// </summary>
        public void DoTest()
        {
            log.Debug("public void DoTest(): called");

            // Create a test message to send.
            IMessage testMessage = channel.CreateTextMessage("test");

            // Send the desired number of test messages.
            for (int i = 0; i < numMessages; i++)
            {
                publisher.Send(testMessage);
            }

            log.Debug("Sent " + numMessages + " test messages.");

            // Send the report request.
            IMessage reportRequestMessage = channel.CreateTextMessage("Report request message.");
            reportRequestMessage.Headers["TYPE"] = "REPORT_REQUEST";
            
            reportRequestMessage.Headers.SetBoolean("BOOLEAN", false);
            //reportRequestMessage.Headers.SetByte("BYTE", 5);
            reportRequestMessage.Headers.SetDouble("DOUBLE", 3.141);
            reportRequestMessage.Headers.SetFloat("FLOAT", 1.0f);
            reportRequestMessage.Headers.SetInt("INT", 1);
            reportRequestMessage.Headers.SetLong("LONG", 1);
            reportRequestMessage.Headers.SetString("STRING", "hello");
            reportRequestMessage.Headers.SetShort("SHORT", 2);

            publisher.Send(reportRequestMessage);

            log.Debug("Sent the report request message, waiting for all replies...");

            // Wait until all the reports come in.
            allReportsReceivedEvt.WaitOne(TIMEOUT, true);

            // Check if all reports were really received or if the timeout occurred.
            if (numSubscribers == 0)
            {
                log.Debug("Got all reports.");
            }
            else
            {
                log.Debug("Waiting for reports timed out, still waiting for " + numSubscribers + ".");
            }

            // Send the termination request.
            IMessage terminationRequestMessage = channel.CreateTextMessage("Termination request message.");
            terminationRequestMessage.Headers["TYPE"] =  "TERMINATION_REQUEST";
            publisher.Send(terminationRequestMessage);

            log.Debug("Sent the termination request message.");

            // Close all message producers and consumers and the connection to the broker.
            Shutdown();
        }

        /// <summary>
        /// Handles all report messages from subscribers. This decrements the count of subscribers that are still to reply, until this becomes
        /// zero, at which time waiting threads are notified of this event.
        /// </summary>
        /// 
        /// <param name="message">The received report message.</param>
        public void OnMessage(IMessage message)
        {
            log.Debug("public void OnMessage(IMessage message = " + message + "): called");

            // Decrement the count of expected messages and release the wait monitor when this becomes zero.
            if (--numSubscribers == 0)
            {
                log.Debug("Got reports from all subscribers.");
                allReportsReceivedEvt.Set();
            }
        }

        /// <summary> Stops the message consumers and closes the connection. </summary>
        private void Shutdown()
        {
            connection.Stop();
            publisher.Dispose();
            channel.Dispose();
            connection.Dispose();
        }
    }
}
