/*
 * Copyright (C) 2023  Purism SPC
 * SPDX-License-Identifier: GPL-3.0+
 * Author: Guido Günther <agx@sigxcpu.org>
 *
 * See Documentation/ABI/testing/sysfs-class-led-trigger-pattern
 */

#define G_LOG_DOMAIN "fbd-dev-led"

#include "fbd-dev-led-priv.h"
#include "fbd-dev-led-multicolor.h"
#include "fbd-enums.h"
#include "fbd-udev.h"

#include <gio/gio.h>

#define LED_MULTI_INDEX_ATTR     "multi_index"
#define LED_MULTI_INDEX_RED      "red"
#define LED_MULTI_INDEX_GREEN    "green"
#define LED_MULTI_INDEX_BLUE     "blue"
#define LED_MULTI_INTENSITY_ATTR "multi_intensity"


typedef struct _FbdDevLedMulticolor {
  FbdDevLed           parent;

  guint               red_index;
  guint               green_index;
  guint               blue_index;
} FbdDevLedMulticolor;


G_DEFINE_TYPE (FbdDevLedMulticolor, fbd_dev_led_multicolor, FBD_TYPE_DEV_LED)


static gboolean
fbd_dev_led_probe_multicolor (FbdDevLed *led, GError **error)
{
  g_autoptr (GError) err = NULL;
  FbdDevLedMulticolor *self = FBD_DEV_LED_MULTICOLOR (led);
  GUdevDevice *dev = fbd_dev_led_get_device (led);
  const gchar *name, *path;
  const gchar * const *index;
  guint counter = 0;
  guint max_brightness;

  name = g_udev_device_get_name (dev);

  index = g_udev_device_get_sysfs_attr_as_strv (dev, LED_MULTI_INDEX_ATTR);
  if (index == NULL) {
    g_set_error (error,
               G_FILE_ERROR, G_FILE_ERROR_FAILED,
               "%s is no multicololor LED", name);
    return FALSE;
  }

  max_brightness = g_udev_device_get_sysfs_attr_as_int (dev, LED_MAX_BRIGHTNESS_ATTR);
  if (!max_brightness) {
    g_set_error (error,
               G_FILE_ERROR, G_FILE_ERROR_FAILED,
               "%s has no max_brightness", name);
    return FALSE;
  }
  fbd_dev_led_set_max_brightness (led, max_brightness);
  fbd_dev_led_set_color (led, FBD_FEEDBACK_LED_COLOR_RGB);

  for (int i = 0; index[i] != NULL; i++) {
    g_debug ("Index: %s", index[i]);
    if (g_strcmp0 (index[i], LED_MULTI_INDEX_RED) == 0) {
      self->red_index = counter;
      counter++;
    } else if (g_strcmp0 (index[i], LED_MULTI_INDEX_GREEN) == 0) {
      self->green_index = counter;
      counter++;
    } else if (g_strcmp0 (index[i], LED_MULTI_INDEX_BLUE) == 0) {
      self->blue_index = counter;
      counter++;
    } else {
      g_warning ("Unsupport LED color index: %d %s", counter, index[i]);
    }
  }

  path = g_udev_device_get_sysfs_path (dev);
  g_debug ("LED at '%s' usable as multicolor", path);
  return TRUE;
}


static gboolean
fbd_dev_led_start_periodic_multicolor (FbdDevLed           *led,
                                       FbdFeedbackLedColor  color,
                                       guint                max_brightness_percentage,
                                       guint                freq)
{
  FbdDevLedMulticolor *self = FBD_DEV_LED_MULTICOLOR (led);
  GUdevDevice *dev = fbd_dev_led_get_device (led);
  g_autofree char *intensity = NULL;
  g_autoptr (GError) err = NULL;
  g_autofree gchar *str = NULL;
  gboolean success = FALSE;
  guint max_brightness;
  guint colors[] = { 0, 0, 0 };

  max_brightness = fbd_dev_led_get_max_brightness (led);
  switch (color) {
  case FBD_FEEDBACK_LED_COLOR_WHITE:
    colors[self->red_index] = max_brightness;
    colors[self->green_index] = max_brightness;
    colors[self->blue_index] = max_brightness;
    break;
  case FBD_FEEDBACK_LED_COLOR_RED:
    colors[self->red_index] = max_brightness;
    colors[self->green_index] = 0;
    colors[self->blue_index] = 0;
    break;
  case FBD_FEEDBACK_LED_COLOR_GREEN:
    colors[self->red_index] = 0;
    colors[self->green_index] = max_brightness;
    colors[self->blue_index] = 0;
    break;
  case FBD_FEEDBACK_LED_COLOR_BLUE:
    colors[self->red_index] = 0;
    colors[self->green_index] = 0;
    colors[self->blue_index] = max_brightness;
    break;
  default:
    g_warning("Unhandled color: %d\n", color);
    return FALSE;
  }

  intensity = g_strdup_printf ("%d %d %d\n", colors[0], colors[1], colors[2]);
  fbd_dev_led_set_brightness (led, max_brightness);
  success = fbd_udev_set_sysfs_path_attr_as_string (dev,
                                                    LED_MULTI_INTENSITY_ATTR,
                                                    intensity,
                                                    &err);
  if (!success) {
    g_warning ("Failed to set multi intensity: %s", err->message);
    return FALSE;
  }

  /* Chain up to parent class to set the pattern */
  return FBD_DEV_LED_CLASS (fbd_dev_led_multicolor_parent_class)->start_periodic (
    led, color, max_brightness_percentage, freq);
}


static gboolean
fbd_dev_led_has_color_multicolor (FbdDevLed *led, FbdFeedbackLedColor color)
{
  return color == FBD_FEEDBACK_LED_COLOR_RGB;
}


static void
fbd_dev_led_multicolor_class_init (FbdDevLedMulticolorClass *klass)
{
  FbdDevLedClass *fbd_dev_led_class = FBD_DEV_LED_CLASS (klass);

  fbd_dev_led_class->probe = fbd_dev_led_probe_multicolor;
  fbd_dev_led_class->start_periodic = fbd_dev_led_start_periodic_multicolor;
  fbd_dev_led_class->has_color = fbd_dev_led_has_color_multicolor;
}


static void
fbd_dev_led_multicolor_init (FbdDevLedMulticolor *self)
{
}


FbdDevLed *
fbd_dev_led_multicolor_new (GUdevDevice *dev, GError **err)
{
  return g_initable_new (FBD_TYPE_DEV_LED_MULTICOLOR, NULL, err, "dev", dev, NULL);
}
