/*
 * Copyright (C) 2019 Purism SPC
 * SPDX-License-Identifier: GPL-3.0+
 * Author: Guido Günther <agx@sigxcpu.org>
 */

#define G_LOG_DOMAIN "gthd-cooling-device"

#include "gth-names.h"
#include "gthd.h"
#include "gthd-cooling-device.h"
#include "sysfs-utils.h"

#include <errno.h>

#define GTHD_COOLING_DEVICES_DBUS_PATH GTH_DBUS_PATH "/CoolingDevice"

enum {
      PROP_0,
      PROP_NAME,
      PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];

typedef struct _GthdCoolingDevice {
  GthGdbusCoolingDeviceSkeleton parent;

  gchar *name;
  int id;

  guint state_id;
} GthdCoolingDevice;

static void initable_iface_init (GInitableIface *iface);

G_DEFINE_TYPE_EXTENDED (GthdCoolingDevice, gthd_cooling_device,
			GTH_GDBUS_TYPE_COOLING_DEVICE_SKELETON, 0,
			G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))

static void
gthd_cooling_device_set_property (GObject     *object,
                                  guint         property_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  GthdCoolingDevice *self = GTHD_COOLING_DEVICE (object);

  switch (property_id) {
  case PROP_NAME:
    g_free (self->name);
    self->name = g_value_dup_string (value);
    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NAME]);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
gthd_cooling_device_get_property (GObject    *object,
                                  guint       property_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  GthdCoolingDevice *self = GTHD_COOLING_DEVICE (object);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, self->name);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static gboolean
update_state (GthdCoolingDevice *self)
{
  g_autofree gchar *dir = NULL;
  gint new_state, old_state;

  dir = g_build_filename (GTHD_THERMAL_SYSFS, self->name, NULL);

  new_state = sysfs_get_int (dir, "cur_state");
  g_object_get (self, "current-state", &old_state, NULL);
  if (new_state != old_state) {
    g_object_set (self, "current-state", new_state, NULL);
    g_debug ("%s: state %d ", self->name, new_state);
  }

  return G_SOURCE_CONTINUE;
}


static gboolean
init_state (GthdCoolingDevice *self)
{
  update_state (self);
  return G_SOURCE_REMOVE;
}


static gboolean
initable_init (GInitable     *initable,
               GCancellable  *cancellable,
               GError       **error)
{
  GthdCoolingDevice *self = GTHD_COOLING_DEVICE (initable);
  gchar *endptr = NULL;
  gchar *pos;
  gint64 id;
  gint max_state;
  g_autofree gchar *dir = NULL;
  g_autofree gchar *type = NULL;

  errno = 0;
  id = g_ascii_strtoll (&self->name[strlen(GTHD_COOLING_DEVICE_SYSFS)], &endptr, 10);
  if (id == 0 && &self->name[strlen(GTHD_COOLING_DEVICE_SYSFS)] == endptr) {
    g_set_error (error, gthd_error_quark(), GTHD_ERROR_FAILED,
		 "Failed to parse cooling device '%s'", self->name);
    return FALSE;
  }

  if (id == 0 && (errno == EINVAL || errno == ERANGE)) {
    g_set_error (error, gthd_error_quark(), GTHD_ERROR_FAILED,
		 "Failed to parse cooling device '%s'", self->name);
    return FALSE;
  }

  if (id > G_MAXINT) {
    g_set_error (error, gthd_error_quark(), GTHD_ERROR_FAILED,
		 "Failed to parse cooling device '%s'", self->name);
    return FALSE;
  }

  self->id = id;
  dir = g_build_filename (GTHD_THERMAL_SYSFS, self->name, NULL);
  type = sysfs_get_string (dir, "type");
  if ((pos = strchr(type, '\n')) != NULL)
    *pos = '\0';
  g_object_set (self, "type", type, NULL);

  max_state = sysfs_get_int (dir, "max_state");
  g_object_set (self, "max-state", max_state, NULL);

  g_debug("%s: id: %d, type: %s", self->name, self->id, type);

  self->state_id = g_timeout_add_seconds (GTHD_THERMAL_SYSFS_INTERVAL,
                                          (GSourceFunc)update_state,
                                          self);
  /* schedule in idle loop to get data initially quickly */
  g_idle_add ((GSourceFunc)init_state, self);

  return TRUE;
}

static void
initable_iface_init (GInitableIface *iface)
{
    iface->init = initable_init;
}

static void
gthd_cooling_device_dispose (GObject *object)
{
  GthdCoolingDevice *self = GTHD_COOLING_DEVICE (object);

  if (self->state_id) {
    g_source_remove (self->state_id);
    self->state_id = 0;
  }
  G_OBJECT_CLASS (gthd_cooling_device_parent_class)->dispose (object);
}

static void
gthd_cooling_device_finalize (GObject *object)
{
  GthdCoolingDevice *self = GTHD_COOLING_DEVICE (object);

  g_free (self->name);

  G_OBJECT_CLASS (gthd_cooling_device_parent_class)->finalize (object);
}


static void
gthd_cooling_device_class_init (GthdCoolingDeviceClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->set_property = gthd_cooling_device_set_property;
  object_class->get_property = gthd_cooling_device_get_property;

  object_class->dispose = gthd_cooling_device_dispose;
  object_class->finalize = gthd_cooling_device_finalize;

  props[PROP_NAME] =
    g_param_spec_string (
       "name",
       "Name",
       "The cooling device name",
       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
gthd_cooling_device_init (GthdCoolingDevice *self)
{
}


GthdCoolingDevice *
gthd_cooling_device_new (const gchar *name, GError **error)
{
  return GTHD_COOLING_DEVICE (g_initable_new (GTHD_TYPE_COOLING_DEVICE,
					      NULL,
					      error,
					      "name", name,
					      NULL));
}


gint
gthd_cooling_device_get_id (GthdCoolingDevice *self)
{
  g_return_val_if_fail (GTHD_IS_COOLING_DEVICE (self), -1);

  return self->id;
}


const gchar*
gthd_cooling_device_get_name (GthdCoolingDevice *self)
{
  g_return_val_if_fail (GTHD_IS_COOLING_DEVICE (self), NULL);

  return self->name;
}


gchar *
gthd_cooling_device_get_dbus_path (GthdCoolingDevice *self)
{
  g_return_val_if_fail (GTHD_IS_COOLING_DEVICE (self), NULL);
  return g_strdup_printf(GTHD_COOLING_DEVICES_DBUS_PATH "/%d", self->id);
}
