/*
 * Copyright 2014-2016 James Geboski <jgeboski@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _FACEBOOK_MQTT_H_
#define _FACEBOOK_MQTT_H_

/**
 * SECTION:mqtt
 * @section_id: facebook-mqtt
 * @short_description: <filename>facebook-mqtt.h</filename>
 * @title: MQTT Connection
 *
 * The MQTT connection.
 */

#include <string.h>

#include "facebook-glib.h"

#define FB_TYPE_MQTT  (fb_mqtt_get_type())
#define FB_MQTT(obj)  (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_MQTT, FbMqtt))
#define FB_MQTT_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_MQTT, FbMqttClass))
#define FB_IS_MQTT(obj)  (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_MQTT))
#define FB_IS_MQTT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_MQTT))
#define FB_MQTT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_MQTT, FbMqttClass))

#define FB_TYPE_MQTT_MESSAGE  (fb_mqtt_message_get_type())
#define FB_MQTT_MESSAGE(obj)  (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_MQTT_MESSAGE, FbMqttMessage))
#define FB_MQTT_MESSAGE_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_MQTT_MESSAGE, FbMqttMessageClass))
#define FB_IS_MQTT_MESSAGE(obj)  (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_MQTT_MESSAGE))
#define FB_IS_MQTT_MESSAGE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_MQTT_MESSAGE))
#define FB_MQTT_MESSAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_MQTT_MESSAGE, FbMqttMessageClass))

/**
 * FB_MQTT_NAME:
 *
 * The name of the MQTT version.
 */
#define FB_MQTT_NAME  "MQTToT"

/**
 * FB_MQTT_LEVEL:
 *
 * The level of the MQTT version.
 */
#define FB_MQTT_LEVEL  3

/**
 * FB_MQTT_KA:
 *
 * The keep-alive timeout, in seconds, of the MQTT connection.
 */
#define FB_MQTT_KA  60

/**
 * FB_MQTT_HOST:
 *
 * The MQTT host name for Facebook.
 */
#define FB_MQTT_HOST  "mqtt.facebook.com"

/**
 * FB_MQTT_PORT:
 *
 * The MQTT host port for Facebook.
 */
#define FB_MQTT_PORT  443

/**
 * FB_MQTT_TIMEOUT_CONN:
 *
 * The timeout, in milliseconds, to wait for a PING back from the
 * server.
 */
#define FB_MQTT_TIMEOUT_CONN  (FB_MQTT_KA * 1500)

/**
 * FB_MQTT_TIMEOUT_PING:
 *
 * The timeout, in milliseconds, to send a PING to the server.
 */
#define FB_MQTT_TIMEOUT_PING  (FB_MQTT_KA * 1000)

/**
 * FB_MQTT_ERROR:
 *
 * The #GQuark of the domain of MQTT errors.
 */
#define FB_MQTT_ERROR  fb_mqtt_error_quark()

/**
 * FB_MQTT_SSL_ERROR:
 *
 * The #GQuark of the domain of MQTT SSL errors.
 */
#define FB_MQTT_SSL_ERROR  fb_mqtt_ssl_error_quark()

typedef struct _FbMqtt FbMqtt;
typedef struct _FbMqttClass FbMqttClass;
typedef struct _FbMqttPrivate FbMqttPrivate;
typedef struct _FbMqttMessage FbMqttMessage;
typedef struct _FbMqttMessageClass FbMqttMessageClass;
typedef struct _FbMqttMessagePrivate FbMqttMessagePrivate;

/**
 * FbMqttConnectFlags:
 * @FB_MQTT_CONNECT_FLAG_CLR: Clear the session.
 * @FB_MQTT_CONNECT_FLAG_WILL: A will message is in the payload.
 * @FB_MQTT_CONNECT_FLAG_RET: Retain the will message.
 * @FB_MQTT_CONNECT_FLAG_PASS: A password is in the payload.
 * @FB_MQTT_CONNECT_FLAG_USER: A user name is in the payload.
 * @FB_MQTT_CONNECT_FLAG_QOS0: Use no quality of service.
 * @FB_MQTT_CONNECT_FLAG_QOS1: Use level one quality of service.
 * @FB_MQTT_CONNECT_FLAG_QOS2: Use level two quality of service.
 *
 * The #FbMqttMessage flags for the CONNECT message.
 */
typedef enum
{
    FB_MQTT_CONNECT_FLAG_CLR = 1 << 1,
    FB_MQTT_CONNECT_FLAG_WILL = 1 << 2,
    FB_MQTT_CONNECT_FLAG_RET = 1 << 5,
    FB_MQTT_CONNECT_FLAG_PASS = 1 << 6,
    FB_MQTT_CONNECT_FLAG_USER = 1 << 7,
    FB_MQTT_CONNECT_FLAG_QOS0 = 0 << 3,
    FB_MQTT_CONNECT_FLAG_QOS1 = 1 << 3,
    FB_MQTT_CONNECT_FLAG_QOS2 = 2 << 3
} FbMqttConnectFlags;

/**
 * FbMqttError:
 * @FB_MQTT_ERROR_SUCCESS: There is no error.
 * @FB_MQTT_ERROR_PRTVERS: Unacceptable protocol version.
 * @FB_MQTT_ERROR_IDREJECT: Identifier rejected.
 * @FB_MQTT_ERROR_SRVGONE: Server unavailable.
 * @FB_MQTT_ERROR_USERPASS: Bad user name or password.
 * @FB_MQTT_ERROR_UNAUTHORIZED: Not authorized.
 * @FB_MQTT_ERROR_GENERAL: General failure.
 *
 * The error codes for the #FB_MQTT_ERROR domain.
 */
typedef enum
{
    FB_MQTT_ERROR_SUCCESS = 0,
    FB_MQTT_ERROR_PRTVERS = 1,
    FB_MQTT_ERROR_IDREJECT = 2,
    FB_MQTT_ERROR_SRVGONE = 3,
    FB_MQTT_ERROR_USERPASS = 4,
    FB_MQTT_ERROR_UNAUTHORIZED = 5,
    FB_MQTT_ERROR_GENERAL
} FbMqttError;

/**
 * FbMqttMessageFlags:
 * @FB_MQTT_MESSAGE_FLAG_RET: Retain messages.
 * @FB_MQTT_MESSAGE_FLAG_DUP: Duplicate delivery of control packet.
 * @FB_MQTT_MESSAGE_FLAG_QOS0: Use no quality of service.
 * @FB_MQTT_MESSAGE_FLAG_QOS1: Use level one quality of service.
 * @FB_MQTT_MESSAGE_FLAG_QOS2: Use level two quality of service.
 *
 * The #FbMqttMessage flags.
 */
typedef enum
{
    FB_MQTT_MESSAGE_FLAG_RET = 1 << 0,
    FB_MQTT_MESSAGE_FLAG_DUP = 1 << 3,
    FB_MQTT_MESSAGE_FLAG_QOS0 = 0 << 1,
    FB_MQTT_MESSAGE_FLAG_QOS1 = 1 << 1,
    FB_MQTT_MESSAGE_FLAG_QOS2 = 2 << 1
} FbMqttMessageFlags;

/**
 * FbMqttMessageType:
 * @FB_MQTT_MESSAGE_TYPE_CONNECT: Requests a connection.
 * @FB_MQTT_MESSAGE_TYPE_CONNACK: Connection acknowledgment.
 * @FB_MQTT_MESSAGE_TYPE_PUBLISH: Requests a message publication.
 * @FB_MQTT_MESSAGE_TYPE_PUBACK: Publication acknowledgment.
 * @FB_MQTT_MESSAGE_TYPE_PUBREC: Publication received.
 * @FB_MQTT_MESSAGE_TYPE_PUBREL: Publication released.
 * @FB_MQTT_MESSAGE_TYPE_PUBCOMP: Publication complete.
 * @FB_MQTT_MESSAGE_TYPE_SUBSCRIBE: Requests a subscription.
 * @FB_MQTT_MESSAGE_TYPE_SUBACK: Subscription acknowledgment.
 * @FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE: Requests an unsubscription.
 * @FB_MQTT_MESSAGE_TYPE_UNSUBACK: Unsubscription acknowledgment.
 * @FB_MQTT_MESSAGE_TYPE_PINGREQ: Requests a ping response.
 * @FB_MQTT_MESSAGE_TYPE_PINGRESP: Ping response.
 * @FB_MQTT_MESSAGE_TYPE_DISCONNECT: Requests a disconnection.
 *
 * The #FbMqttMessage types.
 */
typedef enum
{
    FB_MQTT_MESSAGE_TYPE_CONNECT = 1,
    FB_MQTT_MESSAGE_TYPE_CONNACK = 2,
    FB_MQTT_MESSAGE_TYPE_PUBLISH = 3,
    FB_MQTT_MESSAGE_TYPE_PUBACK = 4,
    FB_MQTT_MESSAGE_TYPE_PUBREC = 5,
    FB_MQTT_MESSAGE_TYPE_PUBREL = 6,
    FB_MQTT_MESSAGE_TYPE_PUBCOMP = 7,
    FB_MQTT_MESSAGE_TYPE_SUBSCRIBE = 8,
    FB_MQTT_MESSAGE_TYPE_SUBACK = 9,
    FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE = 10,
    FB_MQTT_MESSAGE_TYPE_UNSUBACK = 11,
    FB_MQTT_MESSAGE_TYPE_PINGREQ = 12,
    FB_MQTT_MESSAGE_TYPE_PINGRESP = 13,
    FB_MQTT_MESSAGE_TYPE_DISCONNECT = 14
} FbMqttMessageType;

/**
 * FbMqtt:
 *
 * Represents an MQTT connection.
 */
struct _FbMqtt
{
    /*< private >*/
    GObject parent;
    FbMqttPrivate *priv;
};

/**
 * FbMqttClass:
 *
 * The base class for all #FbMqtt's.
 */
struct _FbMqttClass
{
    /*< private >*/
    GObjectClass parent_class;
};

/**
 * FbMqttMessage:
 *
 * Represents a reader/writer for an MQTT message.
 */
struct _FbMqttMessage
{
    /*< private >*/
    GObject parent;
    FbMqttMessagePrivate *priv;
};

/**
 * FbMqttMessageClass:
 *
 * The base class for all #FbMqttMessageClass's.
 */
struct _FbMqttMessageClass
{
    /*< private >*/
    GObjectClass parent_class;
};

/**
 * fb_mqtt_get_type:
 *
 * Returns: The #GType for an #FbMqtt.
 */
GType
fb_mqtt_get_type(void);

/**
 * fb_mqtt_message_get_type:
 *
 * Returns: The #GType for an #FbMqttMessage.
 */
GType
fb_mqtt_message_get_type(void);

/**
 * fb_mqtt_error_quark:
 *
 * Gets the #GQuark of the domain of MQTT errors.
 *
 * Returns: The #GQuark of the domain.
 */
GQuark
fb_mqtt_error_quark(void);

/**
 * fb_mqtt_ssl_error_quark:
 *
 * Gets the #GQuark of the domain of MQTT SSL errors.
 *
 * Returns: The #GQuark of the domain.
 */
GQuark
fb_mqtt_ssl_error_quark(void);

/**
 * fb_mqtt_new:
 *
 * Creates a new #FbMqtt. The returned #FbMqtt should be freed with
 * #g_object_unref() when no longer needed.
 *
 * Returns: The new #FbMqtt.
 */
FbMqtt *
fb_mqtt_new(void);

/**
 * fb_mqtt_close:
 * @mqtt: The #FbMqtt.
 *
 * Closes the MQTT without sending the `DISCONNECT` message. The #FbMqtt
 * may be reopened after calling this.
 */
void
fb_mqtt_close(FbMqtt *mqtt);

/**
 * fb_mqtt_error:
 * @mqtt: The #FbMqtt.
 * @error: The #FbMqttError.
 * @format: The format string literal.
 * @...: The arguments for @format.
 *
 * Emits an #FbMqttError and closes the #FbMqtt.
 */
void
fb_mqtt_error(FbMqtt *mqtt, FbMqttError error, const gchar *format, ...)
              G_GNUC_PRINTF(3, 4);

/**
 * fb_mqtt_read:
 * @mqtt: The #FbMqtt.
 * @msg: The #FbMqttMessage.
 *
 * Reads an #FbMqttMessage into the #FbMqtt for processing.
 */
void
fb_mqtt_read(FbMqtt *mqtt, FbMqttMessage *msg);

/**
 * fb_mqtt_write:
 * @mqtt: The #FbMqtt.
 * @msg: The #FbMqttMessage.
 *
 * Writes an #FbMqttMessage to the wire.
 */
void
fb_mqtt_write(FbMqtt *mqtt, FbMqttMessage *msg);

/**
 * fb_mqtt_open:
 * @mqtt: The #FbMqtt.
 * @host: The host name.
 * @port: The port.
 *
 * Opens an SSL connection to the remote server.
 */
void
fb_mqtt_open(FbMqtt *mqtt, const gchar *host, gint port);

/**
 * fb_mqtt_connect:
 * @mqtt: The #FbMqtt.
 * @flags: The #FbMqttConnectFlags.
 * @pload: The payload.
 *
 * Sends a message of type #FB_MQTT_MESSAGE_TYPE_CONNECT.
 */
void
fb_mqtt_connect(FbMqtt *mqtt, guint8 flags, const GByteArray *pload);

/**
 * fb_mqtt_connected:
 * @mqtt: The #FbMqtt.
 * @error: #TRUE to error with no connection, otherwise #FALSE.
 *
 * Determines the connection state of the #FbMqtt, and optionally emits
 * an error.
 *
 * Returns: #TRUE if the #FbMqtt is connected, otherwise #FALSE.
 */
gboolean
fb_mqtt_connected(FbMqtt *mqtt, gboolean error);

/**
 * fb_mqtt_disconnect:
 * @mqtt: The #FbMqtt.
 *
 * Sends a message of type #FB_MQTT_MESSAGE_TYPE_DISCONNECT, and closes
 * the connection.
 */
void
fb_mqtt_disconnect(FbMqtt *mqtt);

/**
 * fb_mqtt_publish:
 * @mqtt: The #FbMqtt.
 * @topic: The topic.
 * @pload: The payload.
 *
 * Sends a message of type #FB_MQTT_MESSAGE_TYPE_PUBLISH.
 */
void
fb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, const GByteArray *pload);

/**
 * fb_mqtt_subscribe:
 * @mqtt: The #FbMqtt.
 * @topic1: The first topic.
 * @qos1: The first QoS.
 * @...: The %NULL-terminated list of topic/QoS pairs.
 *
 * Sends a message of type #FB_MQTT_MESSAGE_TYPE_SUBSCRIBE.
 */
void
fb_mqtt_subscribe(FbMqtt *mqtt, const gchar *topic1, guint16 qos1, ...)
                  G_GNUC_NULL_TERMINATED;

/**
 * fb_mqtt_unsubscribe:
 * @mqtt: The #FbMqtt.
 * @topic1: The first topic.
 * @...: The %NULL-terminated list of topics.
 *
 * Sends a message of type #FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE.
 */
void
fb_mqtt_unsubscribe(FbMqtt *mqtt, const gchar *topic1, ...)
                    G_GNUC_NULL_TERMINATED;

/**
 * fb_mqtt_message_new:
 * @type: The #FbMqttMessageType.
 * @flags: The #FbMqttMessageFlags.
 *
 * Creates a new #FbMqttMessage. The returned #FbMqttMessage should be
 * freed with #g_object_unref() when no longer needed.
 *
 * Returns: The new #FbMqttMessage.
 */
FbMqttMessage *
fb_mqtt_message_new(FbMqttMessageType type, FbMqttMessageFlags flags);

/**
 * fb_mqtt_message_new_bytes:
 * @bytes: The #GByteArray.
 *
 * Creates a new #FbMqttMessage from a #GByteArray. The returned
 * #FbMqttMessage should be freed with #g_object_unref() when no
 * longer needed.
 *
 * Returns: The new #FbMqttMessage.
 */
FbMqttMessage *
fb_mqtt_message_new_bytes(GByteArray *bytes);

/**
 * fb_mqtt_message_reset:
 * @msg: The #FbMqttMessage.
 *
 * Resets an #FbMqttMessage. This resets the cursor position, and
 * removes any sort of fixed header.
 */
void
fb_mqtt_message_reset(FbMqttMessage *msg);

/**
 * fb_mqtt_message_bytes:
 * @msg: The #FbMqttMessage.
 *
 * Formats the internal #GByteArray of the #FbMqttMessage with the
 * required fixed header. This resets the cursor position.
 *
 * Returns: The internal #GByteArray.
 */
const GByteArray *
fb_mqtt_message_bytes(FbMqttMessage *msg);

/**
 * fb_mqtt_message_read:
 * @msg: The #FbMqttMessage.
 * @data: The data buffer.
 * @size: The size of @buffer.
 *
 * Reads data from the #FbMqttMessage into a buffer. If @data is #NULL,
 * this will simply advance the cursor position.
 *
 * Returns: #TRUE if the data was read, otherwise #FALSE.
 */
gboolean
fb_mqtt_message_read(FbMqttMessage *msg, gpointer data, guint size);

/**
 * fb_mqtt_message_read_r:
 * @msg: The #FbMqttMessage.
 * @bytes: The #GByteArray.
 *
 * Reads the remaining data from the #FbMqttMessage into a #GByteArray.
 * This is useful for obtaining the payload of a message.
 *
 * Returns: #TRUE if the data was read, otherwise #FALSE.
 */
gboolean
fb_mqtt_message_read_r(FbMqttMessage *msg, GByteArray *bytes);

/**
 * fb_mqtt_message_read_byte:
 * @msg: The #FbMqttMessage.
 * @value: The return location for the value or #NULL.
 *
 * Reads an 8-bit integer value from the #FbMqttMessage. If @value is
 * #NULL, this will simply advance the cursor position.
 *
 * Returns: #TRUE if the value was read, otherwise #FALSE.
 */
gboolean
fb_mqtt_message_read_byte(FbMqttMessage *msg, guint8 *value);

/**
 * fb_mqtt_message_read_mid:
 * @msg: The #FbMqttMessage.
 * @value: The return location for the value or #NULL.
 *
 * Reads a message identifier from the #FbMqttMessage. If @value is
 * #NULL, this will simply advance the cursor position.
 *
 * Returns: #TRUE if the value was read, otherwise #FALSE.
 */
gboolean
fb_mqtt_message_read_mid(FbMqttMessage *msg, guint16 *value);

/**
 * fb_mqtt_message_read_u16:
 * @msg: The #FbMqttMessage.
 * @value: The return location for the value or #NULL.
 *
 * Reads a 16-bit integer value from the #FbMqttMessage. If @value is
 * #NULL, this will simply advance the cursor position.
 *
 * Returns: #TRUE if the value was read, otherwise #FALSE.
 */
gboolean
fb_mqtt_message_read_u16(FbMqttMessage *msg, guint16 *value);

/**
 * fb_mqtt_message_read_str:
 * @msg: The #FbMqttMessage.
 * @value: The return location for the value or #NULL.
 *
 * Reads a string value from the #FbMqttMessage. The value returned to
 * @value should be freed with #g_free() when no longer needed. If
 * @value is #NULL, this will simply advance the cursor position.
 *
 * Returns: #TRUE if the value was read, otherwise #FALSE.
 */
gboolean
fb_mqtt_message_read_str(FbMqttMessage *msg, gchar **value);

/**
 * fb_mqtt_message_write:
 * @msg: The #FbMqttMessage.
 * @data: The data buffer.
 * @size: The size of @buffer.
 *
 * Writes data to the #FbMqttMessage.
 */
void
fb_mqtt_message_write(FbMqttMessage *msg, gconstpointer data, guint size);

/**
 * fb_mqtt_message_write_byte:
 * @msg: The #FbMqttMessage.
 * @value: The value.
 *
 * Writes an 8-bit integer value to the #FbMqttMessage.
 */
void
fb_mqtt_message_write_byte(FbMqttMessage *msg, guint8 value);

/**
 * fb_mqtt_message_write_mid:
 * @msg: The #FbMqttMessage.
 * @value: The value.
 *
 * Writes a message identifier to the #FbMqttMessage. This increments
 * @value for the next message.
 */
void
fb_mqtt_message_write_mid(FbMqttMessage *msg, guint16 *value);

/**
 * fb_mqtt_message_write_u16:
 * @msg: The #FbMqttMessage.
 * @value: The value.
 *
 * Writes a 16-bit integer value to the #FbMqttMessage.
 */
void
fb_mqtt_message_write_u16(FbMqttMessage *msg, guint16 value);

/**
 * fb_mqtt_message_write_str:
 * @msg: The #FbMqttMessage.
 * @value: The value.
 *
 * Writes a string value to the #FbMqttMessage.
 */
void
fb_mqtt_message_write_str(FbMqttMessage *msg, const gchar *value);

#endif /* _FACEBOOK_MQTT_H_ */
