/**
 *
 * Copyright 2004 Protique Ltd
 *
 * Licensed 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.activemq.sampler;

import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
import org.activemq.util.connection.ServerConnectionFactory;
import org.activemq.util.IdGenerator;

import javax.jms.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

/**
 * A sampler which understands Tcp requests.
 */
public class Producer extends Sampler implements TestListener {

    public static int counter;

    private static final Logger log = LoggingManager.getLoggerForClass();

    // Otherwise, the response is scanned for these strings
    private static final String STATUS_PREFIX = JMeterUtils.getPropDefault("tcp.status.prefix", "");
    private static final String STATUS_SUFFIX = JMeterUtils.getPropDefault("tcp.status.suffix", "");
    private static final String STATUS_PROPERTIES = JMeterUtils.getPropDefault("tcp.status.properties", "");

    private static final Properties statusProps = new Properties();
    private static final long INSECONDS = 60;
    private static final long MSGINTERVALINSECS = 60;

    private Timer timerPublish;
    private Timer timerPublishLoop;

    private int batchCounter = 0;

    static {
        log.info("Protocol Handler name=" + getClassname());
        log.info("Status prefix=" + STATUS_PREFIX);
        log.info("Status suffix=" + STATUS_SUFFIX);
        log.info("Status properties=" + STATUS_PROPERTIES);

        if (STATUS_PROPERTIES.length() > 0) {
            File f = new File(STATUS_PROPERTIES);

            try {
                statusProps.load(new FileInputStream(f));
                log.info("Successfully loaded properties");
                //haveStatusProps = true;
            } catch (FileNotFoundException e) {
                log.info("Property file not found");
            } catch (IOException e) {
                log.info("Property file error " + e.toString());
            }
        }
    }

    /**
     * Constructor for ProducerSampler object.
     */
    public Producer() {
        log.debug("Created " + this);
        protocolHandler = this.getProtocol(); //from superclass sampler.
        log.debug("Using Protocol Handler: " + protocolHandler.getClass().getName());
    }

    /**
     * Increments the int variable.
     *
     * @param count - variable incremented.
     */
    protected synchronized void count(int count) {
        counter += count;
    }

    /**
     * @return Returns the message.
     */
    protected String getMessage() {
        StringBuffer buffer = new StringBuffer();

        for (int i = 0; i < getMsgSize(); i++) {
            char ch = 'X';
            buffer.append(ch);
        }

        return buffer.toString();
    }

    /**
     * Retrieves the message then sends it via tcp.
     *
     * @throws Exception
     */
    protected void publish() throws Exception {
        long threadRampUp = 0;

        if (getNoProducer() > 0) {
            threadRampUp = (long) ((double) (getRampUp() * INSECONDS) / ((double) getNoProducer()) * 1000);
        }

        timerPublish = new Timer();
        timerPublish.scheduleAtFixedRate(new newThread(), 0, threadRampUp);
    }

    /**
     * Sends the information from the client via tcp.
     *
     * @param text    - message that is sent.
     * @param subject - subject of the message to be sent.
     * @throws JMSException
     */
    protected void publish(String text, String subject) throws JMSException {

        Destination destination = null;
        Session session = null;
        MessageProducer publisher = null;
        Connection connection = null;

        connection = ServerConnectionFactory.createConnectionFactory(this.getURL(),
                                                                     this.getMQServer(),
                                                                     this.getTopic(),
                                                                     this.getEmbeddedBroker());

        if (this.getDurable()) {
            if (ServerConnectionFactory.JORAM_SERVER.equals(this.getMQServer())) {
                //Id set be server

            } else {
                IdGenerator idGenerator = new IdGenerator();
                connection.setClientID(idGenerator.generateId());
            }
        }

        session = ServerConnectionFactory.createSession(connection,
                                                        this.getTransacted(),
                                                        this.getMQServer(),
                                                        this.getTopic());

        destination = ServerConnectionFactory.createDestination(session,
                                                                subject,
                                                                this.getURL(),
                                                                this.getMQServer(),
                                                                this.getTopic());

        if (ServerConnectionFactory.OPENJMS_SERVER.equals(this.getMQServer())) {
            if (this.getTopic()){
                // OpenJMS specific, topic message publisher
                connection.start();
                TopicPublisher topicPublisher = ((TopicSession)session).createPublisher((Topic)destination);
                publisher = topicPublisher;

            } else {
                // OpenJMS specific, queue message publisher
                connection.start();
                QueueSender queuePublisher = ((QueueSession)session).createSender((Queue)destination);
                publisher = queuePublisher;

            }

        } else {
            publisher = session.createProducer(destination);
        }

        long msgIntervalInMins = this.getMsgInterval();
        long msgIntervalInSecs = msgIntervalInMins * INSECONDS;

        if (msgIntervalInSecs < 0) {
            msgIntervalInSecs = MSGINTERVALINSECS;
        }

        if (getDurable()) {
            publisher.setDeliveryMode(DeliveryMode.PERSISTENT);
        } else {
            publisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
        }

        if (this.getDefMsgInterval()) {
            while (!stopThread) {
                publishLoop(session, publisher, text);
            }
            ServerConnectionFactory.close(connection, session);

        } else {
            // set the session, publisher and connection.
            this.setSession(session);
            this.setPublisher(publisher);
            this.setConnection(connection);

            timerPublishLoop = new Timer();
            timerPublishLoop.scheduleAtFixedRate(new publish(), 0, msgIntervalInSecs * 1000);

        }

    }

    /**
     * Sends a message through MessageProducer object.
     *
     * @param session   - Session oject.
     * @param publisher - MessageProducer object.
     * @param text      - text that is used to create Message object.
     * @throws JMSException
     */
    protected void publishLoop(Session session, MessageProducer publisher, String text) throws JMSException {
        if (ServerConnectionFactory.OPENJMS_SERVER.equals(this.getMQServer())) {
            if (publisher instanceof TopicPublisher) {
                // OpenJMS specific, topic message sender
                Message message = ((TopicSession)session).createTextMessage(text);
                ((TopicPublisher)publisher).publish(message);

            } else if (publisher instanceof QueueSender){
                // OpenJMS specific, queue message sender
                Message message = ((QueueSession)session).createTextMessage(text);
                ((QueueSender)publisher).send(message);

            }
        }
        else if (ServerConnectionFactory.JORAM_SERVER.equals(this.getMQServer())) {
            Message message = session.createTextMessage(text);
            publisher.send(message);
        }
        else {
            Message message = session.createTextMessage(text);
            publisher.send(message);
        }

        if (this.getTransacted()) {
            batchCounter++;

            if (batchCounter == this.getBatchSize()) {
                batchCounter = 0;
                session.commit();
            }
        }
        count(1);
    }

    /**
     * @return the current number of messages sent.
     */
    public static synchronized int resetCount() {
        int answer = counter;
        counter = 0;
        return answer;
    }

    /**
     * Runs and publish the message.
     *
     * @throws Exception
     */
    public void run() throws Exception {

        start();
        publish();
    }

    /**
     * Retrieves the sample as SampleResult object. There are times that this
     * is ignored.
     *
     * @param e - Entry object.
     * @return Returns the sample result.
     */
    public SampleResult sample(Entry e) { // Entry tends to be ignored ...
        SampleResult res = new SampleResult();
        res.setSampleLabel(getName());
        res.setSamplerData(getURL());
        res.sampleStart();

        try {
            //run the benchmark tool code
            this.run();
        } catch (Exception ex) {
            log.debug("Error running producer ", ex);
            res.setResponseCode("500");
            res.setResponseMessage(ex.toString());
        }

        //Calculate response time
        res.sampleEnd();

        // Set if we were successful or not
        res.setSuccessful(true);

        return res;
    }

    /**
     * Logs the end of the test. This is called only once per
     * class.
     */
    public void testEnded() {
        log.debug(this + " test ended");
    }

    /**
     * Logs the host at the end of the test.
     *
     * @param host - the host to be logged.
     */
    public void testEnded(String host) {
        log.debug(this + " test ended on " + host);
    }

    /**
     * Logs the start of the test. This is called only once
     * per class.
     */
    public void testStarted() {
        log.debug(this + " test started");
    }

    /**
     * Logs the host at the start of the test.     *
     * @param host - the host to be logged.
     */
    public void testStarted(String host) {
        log.debug(this + " test started on " + host);
    }

    /**
     * Logs the iteration event.
     *
     * @param event
     */
    public void testIterationStart(LoopIterationEvent event) {
        log.debug(this + " test iteration start on " + event.getIteration());
    }

    /**
     *   Creates thread for publishing messages.
     */
    class newThread extends TimerTask {
        final String text = getMessage();
        int numberOfProducer = getNoProducer();
        int counter = 0;

        public void run() {
             if (counter < numberOfProducer) {
                 final String subject = subjects[counter % getNoSubject()];

                 counter++;

                 Thread thread = new Thread() {
                    public void run() {
                        try {
                              if (stopThread) {
                                  return;
                              } else {
                                  publish(text, subject);
                              }
                        } catch (JMSException e) {
                            log.error("Error publishing message ", e);
                        }
                    }
                };

                thread.start();

             } else {
                 timerPublish.cancel();
             }
        }
    }

    /**
     * Starts the publish loop timer.
     */
    class publish extends TimerTask {
        public void run() {
            try {
                if (!stopThread) {
                    publishLoop(getSession(), getPublisher(), getMessage());
                } else {
                    ServerConnectionFactory.close(getConnection(), getSession());
                    timerPublishLoop.cancel();
                }
            } catch(JMSException e) {
                log.error("Could not publish "+e);
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("##########################################");
        System.out.println(" Producer * start *");
        System.out.println("##########################################");

        Producer prod = new Producer();

        if (args.length == 0 ){
            displayToolParameters();
        }

        if (args.length > 0){
            String mqServer = args[0];

            if (mqServer.equalsIgnoreCase("SONICMQ")){
                prod.setMQServer(ServerConnectionFactory.SONICMQ_SERVER);
            } else if (mqServer.equalsIgnoreCase("TIBCOMQ")) {
                prod.setMQServer(ServerConnectionFactory.TIBCOMQ_SERVER);
            } else if (mqServer.equalsIgnoreCase("JBOSSMQ")) {
                prod.setMQServer(ServerConnectionFactory.JBOSSMQ_SERVER);
            } else if (mqServer.equalsIgnoreCase("OPENJMS")) {
                prod.setMQServer(ServerConnectionFactory.OPENJMS_SERVER);
            } else if (mqServer.equalsIgnoreCase("JORAM")) {
                prod.setMQServer(ServerConnectionFactory.JORAM_SERVER);
            } else if (mqServer.equalsIgnoreCase("ACTIVEMQ")) {
                //Run with the default broker
            } else {
                System.out.print("Please enter a valid mq server: [ ");
                System.out.print("SONICMQ | " );
                System.out.print("TIBCOMQ | " );
                System.out.print("JBOSSMQ | " );
                System.out.print("OPENJMS | " );
                System.out.print("ACTIVEMQ |");
                System.out.println("JORAM  ]");
            }

        }

        if (args.length > 1) {
            prod.setURL(args[1]);
        } else {
            System.out.println("Please specify the URL.");
        }

        if (args.length > 2) {
            prod.setDuration(args[2]);
        }

        if (args.length > 3) {
            prod.setRampUp(args[3]);
        }

        if (args.length > 4) {
            prod.setNoProducer(args[4]);
        }

        if (args.length > 5) {
            prod.setNoSubject(args[5]);
        }

        if (args.length > 6) {
            prod.setMsgSize(args[6]);
        }

        if (args.length > 7) {
            prod.setDurable(args[7]);
        }

        if (args.length > 8) {
            prod.setTopic(args[8]);
        }

        if (args.length > 9) {
            prod.setTransacted(args[9]);

            if (args.length > 10) {
                prod.setBatchSize(args[10]);
            } else {
                displayToolParameters();
                System.out.println("Please specify the batch size.");
                return;
            }
        }

        if (args.length > 11) {
            prod.setDefMsgInterval(args[11]);
            if (!prod.getDefMsgInterval()) {
                if (args.length > 12) {
                    prod.setMsgInterval(args[12]);
                } else {
                    displayToolParameters();
                    System.out.println("Please specify the message interval.");
                    return;
                 }
             }
        }

        prod.setDefMsgInterval("true");

        System.out.println("Runnning Consumer tool with the following parameters:");
        System.out.println("Server=" + prod.getMQServer());
        System.out.println("URL="+prod.getURL());
        System.out.println("Duration="+prod.getDuration());
        System.out.println("Ramp up="+prod.getRampUp());
        System.out.println("No. Producer="+prod.getNoProducer());
        System.out.println("No. Subject="+prod.getNoSubject());
        System.out.println("Msg Size="+prod.getMsgSize());
        System.out.println("Is Durable="+prod.getDurable());
        System.out.println("Is Topic="+prod.getTopic());
        System.out.println("Is Transacted="+prod.getTransacted());

        try {
            prod.run();
        } catch (Exception e){
            System.out.println("Excception e="+e);

        }
        System.out.println("##########################################");
        System.out.println("Producer * end *");
        System.out.println("##########################################");
    }

    private static void displayToolParameters(){
        System.out.println("Producer tool usage: ");
        System.out.print("[Message Queue Server] ");
        System.out.print("[URL] ");
        System.out.print("[Duration] ");
        System.out.print("[Ramp up] ");
        System.out.print("[No. of producer] ");
        System.out.print("[No. of subject] ");
        System.out.print("[Message size] ");
        System.out.print("[Delivery mode] ");
        System.out.print("[Is topic] ");
        System.out.print("[Is transacted] ");
        System.out.print("[Batch size] ");
        System.out.print("[Has Message interval] ");
        System.out.println("[Message interval] ");
    }
}