/*
 * Copyright (C) 2025 The Phosh.mobi e.V.
 *
 * SPDX-License-Identifier: GPL-3.0+
 *
 * Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
 */

#include "lcb-message-priv.h"
#include "lcb-enums.h"
#include "lcb-enum-types.h"

enum {
  PROP_0,
  PROP_TEXT,
  PROP_CHANNEL,
  PROP_MSG_CODE,
  PROP_UPDATE,
  PROP_TIMESTAMP,
  PROP_SEVERITY,
  PROP_SEVERITY_SUBJECT,
  PROP_MSG_ID,
  PROP_OPERATOR_CODE,
  PROP_LAST_PROP
};

static GParamSpec *props[PROP_LAST_PROP];

typedef struct _LcbMessagePrivate {
  char            *text;
  guint            channel;
  guint            msg_code;
  guint            update;
  gint64           timestamp;
  LcbSeverityLevel severity;    /* determined at time of receival */
  char            *severity_subject; /* determined/localized at time of receival */
  char            *msg_id;      /* unique and sorts nicely: timestamp-channel-code-update */
  char            *updated_by;  /* contains id of message that updates our message */
  char            *operator_code;
} LcbMessagePrivate;

G_DEFINE_TYPE_WITH_PRIVATE (LcbMessage, lcb_message, G_TYPE_OBJECT);

static void
lcb_message_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
  LcbMessage *self = LCB_MESSAGE (object);
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);
  gint64 _timestamp;

  switch (prop_id) {
  case PROP_TEXT:
    priv->text = g_value_dup_string (value);
    break;

  case PROP_CHANNEL:
    priv->channel = g_value_get_uint (value);
    break;

  case PROP_MSG_CODE:
    priv->msg_code = g_value_get_uint (value);
    break;

  case PROP_UPDATE:
    priv->update = g_value_get_uint (value);
    break;

  case PROP_TIMESTAMP:
    _timestamp = g_value_get_int64 (value);
    if (_timestamp == 0) {
      g_autoptr (GDateTime) now = g_date_time_new_now_local ();

      _timestamp = g_date_time_to_unix (now);
    }

    priv->timestamp = _timestamp;
    break;

  case PROP_SEVERITY:
    priv->severity = g_value_get_flags (value);
    break;

  case PROP_SEVERITY_SUBJECT:
    priv->severity_subject = g_value_dup_string (value);
    break;

  case PROP_OPERATOR_CODE:
    priv->operator_code = g_value_dup_string (value);
    break;

  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
lcb_message_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
  LcbMessage *self = LCB_MESSAGE (object);

  switch (prop_id) {
  case PROP_TEXT:
    g_value_set_string (value, lcb_message_get_text (self));
    break;

  case PROP_CHANNEL:
    g_value_set_uint (value, lcb_message_get_channel (self));
    break;

  case PROP_MSG_CODE:
    g_value_set_uint (value, lcb_message_get_msg_code (self));
    break;

  case PROP_UPDATE:
    g_value_set_uint (value, lcb_message_get_update (self));
    break;

  case PROP_TIMESTAMP:
    g_value_set_int64 (value, lcb_message_get_timestamp (self));
    break;

  case PROP_SEVERITY:
    g_value_set_flags (value, lcb_message_get_severity (self));
    break;

  case PROP_SEVERITY_SUBJECT:
    g_value_set_string (value, lcb_message_get_severity_subject (self));
    break;

  case PROP_MSG_ID:
    g_value_set_string (value, lcb_message_get_msg_id (self));
    break;

  case PROP_OPERATOR_CODE:
    g_value_set_string (value, lcb_message_get_operator_code (self));
    break;

  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
lcb_message_constructed (GObject *object)
{
  LcbMessage *self = LCB_MESSAGE (object);
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  priv->msg_id =
    g_strdup_printf ("%" G_GINT64_FORMAT "-%" G_GUINT16_FORMAT "-%" G_GUINT16_FORMAT "-%" G_GUINT16_FORMAT,
                     priv->timestamp, priv->channel, priv->msg_code, priv->update);

  G_OBJECT_CLASS (lcb_message_parent_class)->constructed (object);
}

static void
lcb_message_dispose (GObject *object)
{
  LcbMessage *self = LCB_MESSAGE (object);
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_clear_pointer (&priv->text, g_free);
  g_clear_pointer (&priv->msg_id, g_free);
  g_clear_pointer (&priv->severity_subject, g_free);
  g_clear_pointer (&priv->operator_code, g_free);

  G_OBJECT_CLASS (lcb_message_parent_class)->dispose (object);
}

static void
lcb_message_class_init (LcbMessageClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->set_property = lcb_message_set_property;
  object_class->get_property = lcb_message_get_property;
  object_class->constructed = lcb_message_constructed;
  object_class->dispose = lcb_message_dispose;

  /**
   * LcbMessage:text:
   *
   * The body of a cell broadcast message
   */
  props[PROP_TEXT] =
    g_param_spec_string ("text", "", "", NULL,
                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * LcbMessage:channel:
   *
   * The channel of the message.
   */
  props[PROP_CHANNEL] =
    g_param_spec_uint ("channel", "", "",
                       0, G_MAXUINT16, 0,
                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * LcbMessage:message-code:
   *
   * The message code
   */
  props[PROP_MSG_CODE] =
    g_param_spec_uint ("message-code", "", "",
                       0, G_MAXUINT16, 0,
                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * LcbMessage:update:
   *
   * When this message updates an earlier message
   * it will have a value greater than 0.
   * A "new" message will have `0` here.
   */
  props[PROP_UPDATE] =
    g_param_spec_uint ("update", "", "",
                       0, 65535, 0,
                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * LcbMessage:timestamp:
   *
   * Timestamp in seconds in UTC.
   * Setting the special value "0" results in the current timestamp.
   */
  props[PROP_TIMESTAMP] =
    g_param_spec_int64 ("timestamp", "", "",
                        0, G_MAXINT64, 0,
                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * LcbMessage:severity:
   *
   * The severity level of this message as determined at time of receival
   */
  props[PROP_SEVERITY] =
    g_param_spec_flags ("severity", "", "",
                        LCB_TYPE_SEVERITY_LEVEL,
                        LCB_SEVERITY_LEVEL_UNKNOWN,
                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * LcbMessage:severity-subject:
   *
   * The subject determined by the severity at the time of receival
   */
  props[PROP_SEVERITY_SUBJECT] =
    g_param_spec_string ("severity-subject", "", "", NULL,
                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * LcbMessage:msg-id:
   *
   * The (unique) id of this message: timestamp-channel-msg_code-update
   */
  props[PROP_MSG_ID] =
    g_param_spec_string ("msg-id", "", "", NULL,
                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * LcbMessage:operator-code:
   *
   * The operator code when the message was received
   */
  props[PROP_OPERATOR_CODE] =
    g_param_spec_string ("operator-code", "", "", NULL,
                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
}

static void
lcb_message_init (LcbMessage *self)
{
}


/**
 * lcb_message_new:
 * @text: The message body
 * @channel: The channel
 * @message_code: Differientates messages within a channel
 * @update: Update number of the message
 * @timestamp: The timestamp of receival
 * @severity: The severity of the message
 *
 * If @timestamp is `0`, set the current time instead.
 *
 * Returns: (transfer full): A newly allocated message.
 */
LcbMessage *
lcb_message_new (const char      *text,
                 guint            channel,
                 guint            message_code,
                 guint            update,
                 gint64           timestamp,
                 LcbSeverityLevel severity,
                 const char      *severity_subject,
                 const char      *operator_code)
{
  return g_object_new (LCB_TYPE_MESSAGE,
                       "text", text,
                       "channel", channel,
                       "message-code", message_code,
                       "update", update,
                       "timestamp", timestamp,
                       "severity", severity,
                       "severity-subject", severity_subject,
                       "operator-code", operator_code,
                       NULL);
}

const char *
lcb_message_get_text (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), NULL);

  return priv->text;
}


guint
lcb_message_get_channel (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), 0);

  return priv->channel;
}


guint
lcb_message_get_msg_code (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), 0);

  return priv->msg_code;
}


guint
lcb_message_get_update (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), 0);

  return priv->update;
}


gint64
lcb_message_get_timestamp (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), 0);

  return priv->timestamp;
}

LcbSeverityLevel
lcb_message_get_severity (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), LCB_SEVERITY_LEVEL_UNKNOWN);

  return priv->severity;
}

const char *
lcb_message_get_severity_subject (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), NULL);

  return priv->severity_subject;
}

const char *
lcb_message_get_msg_id (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), NULL);

  return priv->msg_id;
}

const char *
lcb_message_get_operator_code (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), NULL);

  return priv->operator_code;
}

gboolean
lcb_message_has_been_updated (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), FALSE);

  return !!priv->updated_by;
}

const char *
lcb_message_get_updated_by (LcbMessage *self)
{
  LcbMessagePrivate *priv = lcb_message_get_instance_private (self);

  g_return_val_if_fail (LCB_IS_MESSAGE (self), FALSE);

  return priv->updated_by;
}
