/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * libqmi-glib -- GLib/GIO based library to control QMI devices
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 * Copyright (C) 2012-2017 Aleksander Morgado <aleksander@aleksander.es>
 */

#include <gio/gio.h>

#include "qmi-error-types.h"
#include "qmi-enum-types.h"
#include "qmi-device.h"
#include "qmi-client.h"
#include "qmi-ctl.h"

G_DEFINE_ABSTRACT_TYPE (QmiClient, qmi_client, G_TYPE_OBJECT)

enum {
    PROP_0,
    PROP_DEVICE,
    PROP_SERVICE,
    PROP_CID,
    PROP_VERSION_MAJOR,
    PROP_VERSION_MINOR,
    PROP_VALID,
    PROP_LAST
};

static GParamSpec *properties[PROP_LAST];

struct _QmiClientPrivate {
    QmiDevice *device;
    QmiService service;
    guint8 cid;
    guint version_major;
    guint version_minor;

    guint16 transaction_id;
};

/*****************************************************************************/

GObject *
qmi_client_get_device (QmiClient *self)
{
    GObject *device;

    g_return_val_if_fail (QMI_IS_CLIENT (self), NULL);

    g_object_get (G_OBJECT (self),
                  QMI_CLIENT_DEVICE, &device,
                  NULL);

    return device;
}

GObject *
qmi_client_peek_device (QmiClient *self)
{
    g_return_val_if_fail (QMI_IS_CLIENT (self), NULL);

    return G_OBJECT (self->priv->device);
}

QmiService
qmi_client_get_service (QmiClient *self)
{
    g_return_val_if_fail (QMI_IS_CLIENT (self), QMI_SERVICE_UNKNOWN);

    return self->priv->service;
}

guint8
qmi_client_get_cid (QmiClient *self)
{
    g_return_val_if_fail (QMI_IS_CLIENT (self), QMI_CID_NONE);

    return self->priv->cid;
}

gboolean
qmi_client_is_valid (QmiClient *self)
{
    g_return_val_if_fail (QMI_IS_CLIENT (self), FALSE);

    return (self->priv->service != QMI_SERVICE_UNKNOWN &&
            QMI_IS_DEVICE (self->priv->device) &&
            ((self->priv->cid != QMI_CID_NONE || self->priv->service == QMI_SERVICE_CTL)));
}

gboolean
qmi_client_get_version (QmiClient *self,
                        guint *major,
                        guint *minor)
{
    g_return_val_if_fail (QMI_IS_CLIENT (self), FALSE);

    /* If the major version is greater than zero, assume it was
     * set properly */
    if (!self->priv->version_major)
        return FALSE;

    *major = self->priv->version_major;
    *minor = self->priv->version_minor;
    return TRUE;
}

gboolean
qmi_client_check_version (QmiClient *self,
                          guint major,
                          guint minor)
{
    g_return_val_if_fail (QMI_IS_CLIENT (self), FALSE);

    /* If the major version is greater than zero, assume it was
     * set properly */
    if (!self->priv->version_major)
        return FALSE;

    if (self->priv->version_major > major ||
        (self->priv->version_major == major &&
         self->priv->version_minor >= minor))
        return TRUE;

    return FALSE;
}

guint16
qmi_client_get_next_transaction_id (QmiClient *self)
{
    guint16 next;

    g_return_val_if_fail (QMI_IS_CLIENT (self), 0);

    next = self->priv->transaction_id;

    /* Don't go further than 8bits in the CTL service */
    if ((self->priv->service == QMI_SERVICE_CTL &&
         self->priv->transaction_id == G_MAXUINT8) ||
        self->priv->transaction_id == G_MAXUINT16)
        /* Reset! */
        self->priv->transaction_id = 0x01;
    else
        self->priv->transaction_id++;

    return next;
}

/*****************************************************************************/

void
__qmi_client_process_indication (QmiClient *self,
                                 QmiMessage *message)
{
    if (QMI_CLIENT_GET_CLASS (self)->process_indication)
        QMI_CLIENT_GET_CLASS (self)->process_indication (self, message);
}

/*****************************************************************************/

static void
set_property (GObject *object,
              guint prop_id,
              const GValue *value,
              GParamSpec *pspec)
{
    QmiClient *self = QMI_CLIENT (object);

    switch (prop_id) {
    case PROP_DEVICE:
        /* NOTE!! We do NOT keep a reference to the device here.
         * Clients are OWNED by the device */
        self->priv->device = g_value_get_object (value);
        break;
    case PROP_SERVICE:
        self->priv->service = g_value_get_enum (value);
        break;
    case PROP_CID:
        self->priv->cid = (guint8)g_value_get_uint (value);
        break;
    case PROP_VERSION_MAJOR:
        self->priv->version_major = g_value_get_uint (value);
        break;
    case PROP_VERSION_MINOR:
        self->priv->version_minor = g_value_get_uint (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
    QmiClient *self = QMI_CLIENT (object);

    switch (prop_id) {
    case PROP_DEVICE:
        g_value_set_object (value, self->priv->device);
        break;
    case PROP_SERVICE:
        g_value_set_enum (value, self->priv->service);
        break;
    case PROP_CID:
        g_value_set_uint (value, (guint)self->priv->cid);
        break;
    case PROP_VERSION_MAJOR:
        g_value_set_uint (value, self->priv->version_major);
        break;
    case PROP_VERSION_MINOR:
        g_value_set_uint (value, self->priv->version_minor);
        break;
    case PROP_VALID:
        g_value_set_boolean (value, qmi_client_is_valid (self));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
qmi_client_init (QmiClient *self)
{
    self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
                                              QMI_TYPE_CLIENT,
                                              QmiClientPrivate);

    /* Defaults */
    self->priv->service = QMI_SERVICE_UNKNOWN;
    self->priv->transaction_id = 0x01;
    self->priv->cid = QMI_CID_NONE;
    self->priv->version_major = 0;
    self->priv->version_minor = 0;
}

static void
qmi_client_class_init (QmiClientClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    g_type_class_add_private (object_class, sizeof (QmiClientPrivate));

    object_class->get_property = get_property;
    object_class->set_property = set_property;

    /**
     * QmiClient:client-device:
     *
     * Since: 1.0
     */
    properties[PROP_DEVICE] =
        g_param_spec_object (QMI_CLIENT_DEVICE,
                             "Device",
                             "The QMI device",
                             QMI_TYPE_DEVICE,
                             G_PARAM_READWRITE);
    g_object_class_install_property (object_class, PROP_DEVICE, properties[PROP_DEVICE]);

    /**
     * QmiClient:client-service:
     *
     * Since: 1.0
     */
    properties[PROP_SERVICE] =
        g_param_spec_enum (QMI_CLIENT_SERVICE,
                           "Service",
                           "QMI service this client is using",
                           QMI_TYPE_SERVICE,
                           QMI_SERVICE_UNKNOWN,
                           G_PARAM_READWRITE);
    g_object_class_install_property (object_class, PROP_SERVICE, properties[PROP_SERVICE]);

    /**
     * QmiClient:client-cid:
     *
     * Since: 1.0
     */
    properties[PROP_CID] =
        g_param_spec_uint (QMI_CLIENT_CID,
                           "Client ID",
                           "ID of the client registered into the QMI device",
                           0,
                           G_MAXUINT8,
                           QMI_CID_NONE,
                           G_PARAM_READWRITE);
    g_object_class_install_property (object_class, PROP_CID, properties[PROP_CID]);

    /**
     * QmiClient:client-version-major:
     *
     * Since: 1.0
     */
    properties[PROP_VERSION_MAJOR] =
        g_param_spec_uint (QMI_CLIENT_VERSION_MAJOR,
                           "Version major",
                           "Major version of the service handled by this client",
                           0,
                           G_MAXUINT,
                           0,
                           G_PARAM_READWRITE);
    g_object_class_install_property (object_class, PROP_VERSION_MAJOR, properties[PROP_VERSION_MAJOR]);

    /**
     * QmiClient:client-version-minor:
     *
     * Since: 1.0
     */
    properties[PROP_VERSION_MINOR] =
        g_param_spec_uint (QMI_CLIENT_VERSION_MINOR,
                           "Version minor",
                           "Minor version of the service handled by this client",
                           0,
                           G_MAXUINT,
                           0,
                           G_PARAM_READWRITE);
    g_object_class_install_property (object_class, PROP_VERSION_MINOR, properties[PROP_VERSION_MINOR]);

    /**
     * QmiClient:client-valid:
     *
     * Since: 1.20
     */
    properties[PROP_VALID] =
        g_param_spec_boolean (QMI_CLIENT_VALID,
                              "Valid",
                              "Whether the client is valid and usable",
                              FALSE,
                              G_PARAM_READABLE);
    g_object_class_install_property (object_class, PROP_VALID, properties[PROP_VALID]);
}
