/*
 * Copyright 2016 Hemika Yasinda Kodikara
 *
 * 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.apache.jmeter.protocol.mqtt.paho.clients;

import org.apache.jmeter.protocol.mqtt.data.objects.Message;
import org.apache.jorphan.logging.LoggingManager;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A sample application that demonstrates how to use the Paho MQTT v3.1 Client blocking API.
 * <p/>
 * It can be run from the command line in one of two modes:
 * - as a publisher, sending a single message to a topic on the server
 * - as a subscriber, listening for messages from the server
 * <p/>
 * There are three versions of the sample that implement the same features
 * but do so using using different programming styles:
 * <ol>
 * <li>Sample (this one) which uses the API which blocks until the operation completes</li>
 * <li>SampleAsyncWait shows how to use the asynchronous API with waiters that block until
 * an action completes</li>
 * <li>SampleAsyncCallBack shows how to use the asynchronous API where events are
 * used to notify the application when an action completes<li>
 * </ol>
 * <p/>
 * If the application is run with the -h parameter then info is displayed that
 * describes all of the options / parameters.
 */
public class BlockingClient extends BaseClient {

    private static final org.apache.log.Logger log = LoggingManager.getLoggerForClass();
    private MqttClient client;
    private String brokerUrl;

    /**
     * Constructs an instance of the sample client wrapper
     *
     * @param brokerUrl    the url of the server to connect to
     * @param clientId     the client id to connect with
     * @param cleanSession clear state at end of connection or not (durable or non-durable subscriptions)
     * @param userName     the username to connect with
     * @param password     the password for the user
     * @throws MqttException
     */
    public BlockingClient(String brokerUrl, String clientId, boolean cleanSession, String userName,
                          String password, int keepAlive) throws MqttException {
        this.brokerUrl = brokerUrl;
        String testPlanFileDir = System.getProperty("java.io.tmpdir") + File.separator + "mqtt" + File.separator +
                                 clientId + File.separator + Thread.currentThread().getId();
        MqttDefaultFilePersistence dataStore = new MqttDefaultFilePersistence(testPlanFileDir);

        // Construct the connection options object that contains connection parameters
        // such as cleanSession and LWT
        MqttConnectOptions conOpt = new MqttConnectOptions();
        conOpt.setCleanSession(cleanSession);
        if (password != null && !password.isEmpty()) {
            conOpt.setPassword(password.toCharArray());
        }
        if (userName != null && !userName.isEmpty()) {
            conOpt.setUserName(userName);
        }

        // Setting keep alive time
        conOpt.setKeepAliveInterval(keepAlive);

        // Construct an MQTT blocking mode client
        client = new MqttClient(this.brokerUrl, clientId, dataStore);

        // Set this wrapper as the callback handler
        client.setCallback(this);

        // Connect to the MQTT server
        log.info("Connecting to " + brokerUrl + " with client ID '" + client.getClientId() + "' and cleanSession is " +
                                                                String.valueOf(cleanSession) + " as a blocking client");
        client.connect(conOpt);
        log.info("Connected");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void disconnect() throws MqttException {
        // Disconnect the client
        client.disconnect();
        log.info("Disconnected");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isConnected() {
        return client.isConnected();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void publish(String topicName, int qos, byte[] payload, boolean isRetained) throws MqttException {
        // Create and configure a message
        MqttMessage message = new MqttMessage(payload);
        message.setRetained(isRetained);
        message.setQos(qos);

        // Send the message to the server, control is not returned until
        // it has been delivered to the server meeting the specified
        // quality of service.
        client.publish(topicName, message);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void subscribe(String topicName, int qos) throws MqttException {
        mqttMessageStorage = new ConcurrentLinkedQueue<Message>();
        receivedMessageCounter = new AtomicLong(0);

        // Subscribe to the requested topic
        // The QoS specified is the maximum level that messages will be sent to the client at.
        // For instance if QoS 1 is specified, any messages originally published at QoS 2 will
        // be downgraded to 1 when delivering to the client but messages published at 1 and 0
        // will be received at the same level they were published at.
        log.info("Subscribing to topic \"" + topicName + "\" qos " + qos);
        client.subscribe(topicName, qos);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void connectionLost(Throwable cause) {
        log.info("Connection to " + brokerUrl + " lost!" + cause);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void messageArrived(String topic, MqttMessage message) throws MqttException {
        Message newMessage = new Message(message);
        mqttMessageStorage.add(newMessage);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() throws IOException{
        try {
            client.disconnect();
        } catch (MqttException e) {
            throw new IOException(e.getMessage(), e);
        }
    }
}