/***************************************************************************
 *   Copyright (C) 2025 by David C. Rankin                                      *
 *   david@niflheim                                                               *
 *                                                                         *
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <kmessagebox.h>

#include <qvariant.h>
#include <qslider.h>
#include <qlabel.h>
#include <qspinbox.h>
#include <qlayout.h>
#include <qfile.h>
#include <qdir.h>

#include <dbus/dbus.h>
#include <stdint.h>

#include "kgenericbacklightwidget.h"

kgenericbacklightWidget::kgenericbacklightWidget(QWidget* parent, const char* name, WFlags fl)
                        : kgenericbacklightWidgetBase(parent,name,fl)
{
  /* initialize startup status flag false */
  validbliface = false;

  /* set window name if not set */
  if ( !name ) {
    setName( "KDE Display Backlight" );
  }

  /* affirmatively initialize values zero */
  tickinterval = pagestep = linestep = 0;

  /* create instance of sysfsBlPath to locate and setup paths */
  sysfsbl = new sysfsBlPath (SYSFSBLPATH);

  /* validate backlight interface found and configured */
  if (sysfsbl->valid_blPath() == false) {
    qWarning ("error : no valid backlight interface found");
    /* detailedError messagebox on no valid backlight found */
    kmbx_noValidBacklight();
    kapp->quit();
  }
  else {
    validbliface = true;
  }

  /* set initial backlight values from sysfsbl */
  start_brightness = sysfsbl->get_brightness();
  max_brightness   = sysfsbl->get_maxbrightness();
  brightness       = start_brightness;

  /* show max_brightness label and string */
  str_max_brightness();

  /* set Value and MaxValue range from backlight subsystem */
  slider1->setMaxValue (max_brightness);
  slider1->setValue (brightness);

  spinBox1->setMaxValue (max_brightness);
  spinBox1->setValue (brightness);

  /* compute slider and spinbox properties from max_brightness */
  get_sliderTickInterval();
  get_sliderPageStep();
  get_lineStep();

  /* update slider and spinbox properties */
  slider1->setTickInterval (tickinterval);
  slider1->setPageStep (pagestep);
  slider1->setLineStep (linestep);

  spinBox1->setLineStep (linestep);

  /* move dialog focus to spinbox */
  spinBox1->setFocus();

  /* update language */
  languageChange();

  /* on slider release move focus back to spinbox */
  connect (slider1, SIGNAL(sliderReleased()), spinBox1, SLOT(setFocus()));

  /* connect valueChanged from spinbox to set_backlight slot */
  connect (spinBox1, SIGNAL(valueChanged(int)), this, SLOT(set_backlight(int)));
}

kgenericbacklightWidget::~kgenericbacklightWidget()
{}

/*$SPECIALIZATION$*/


/**
 * @brief convert max_brightness to string representation for label.
 */
QString kgenericbacklightWidget::str_max_brightness()
{
  QString tmp ("Max Brightness :  ");

  strmaxbrightness.setNum (max_brightness);

  tmp.append (strmaxbrightness);

  lblMaxBrightness->setText (tmp);

  return strmaxbrightness;
}


/**
 * @brief return validbliface flag indicating startup success.
 */
bool kgenericbacklightWidget::get_validBlIface()
{
  return validbliface;
}


/**
 * @brief slot for ValueChanged to set backlight via dbus.
 */
void kgenericbacklightWidget::set_backlight (int b)
{
  brightness = b;
  dbusSetBrightness (b);
}


/**
 * @brief qeuryClose() is fired during shutdown providing an opportunity
 * to save any unsaved work before the program ends or save the
 * current window pos, config, etc.. You can set confirming dialog
 * Yes/No. A return of 'true' from queryClose continues exit.
 */
bool kgenericbacklightWidget::queryClose()
{
  /* temporary return true for shutdown to proceed when button clicked */
  return true;
}


/**
 * @brief button to close dialog
 */
void kgenericbacklightWidget::btnClose_clicked()
{
  /* fill in the exit code here */
  if (queryClose()) {
    kapp->quit();
  }
}


/**
 * @brief Sets the strings of the subwidgets using the current
 * language.
 */
void kgenericbacklightWidget::languageChange()
{
  setCaption( tr( "Display Backlight" ) );

  lblHeading->setText( tr( "Adjust Display Backlight" ) );
}


/**
 * @brief Compute slider tick interval.
 */
int kgenericbacklightWidget::get_sliderTickInterval()
{
  if (max_brightness >= 100000) {
    return (tickinterval = 10000);
  }
  else if (max_brightness >= 10000) {
    return (tickinterval = 1000);
  }
  else if (max_brightness >= 1000) {
    return (tickinterval = 100);
  }
  else if (max_brightness >= 100) {
    return(tickinterval =  10);
  }
  else if (max_brightness >= 20) {
    return (tickinterval = max_brightness / 10);
  }

  return (tickinterval = 1);
}


/**
 * @brief Compute slider pagestep.
 */
int kgenericbacklightWidget::get_sliderPageStep()
{
  if (tickinterval <= 0) {
    get_sliderTickInterval();
  }

  if (tickinterval >= 10000) {
    return (pagestep = 1000);
  }
  else if (tickinterval >= 1000) {
    return (pagestep = 100);
  }
  else if (tickinterval >= 100) {
    return (pagestep = 10);
  }

  return (pagestep = tickinterval);
}


/**
 * @brief Computer slider and spinbox linestep.
 */
int kgenericbacklightWidget::get_lineStep()
{
  if (pagestep <= 0) {
    get_sliderPageStep();
  }

  if (pagestep > 1) {
    return (linestep = pagestep / 2);
  }

  return (linestep = 1);
}


/**
 * @brief detailedError messagebox providing information on failure to
 * find a valid backlight interface.
 */
void kgenericbacklightWidget::kmbx_noValidBacklight()
{
  KMessageBox::detailedError (0,
              "Error: no valid backlight interface found",
              "/sys/class/backlight/ was searched for an active backlight "
              "interface and none was found.\n"
              "For NVidia drivers, adding the folllowing kernel parameters "
              "will enable the acpi_video0 backlight interface:\n"
              "  \"acpi_backlight=video\"\n"
              "Intel drivers should provide intel_backlight automatically. "
              "AMD can work though acpi_video0. In addition to the parameter"
              "\"backlight=video\" you can try \"backlight=vendor\" or "
              "\"backlight=native\".",
              "Error No Backlight Interface");
}

/**
 * @brief get property value from dbus interface (iface) with name (name)
 * and fill buffer (result) with max size (sz in bytes) with property. The
 * function below only implements string property results - extend as
 * needed.
 * @param result buffer to hold property retrieved from dbus interface.
 * @param sz maximum size in bytes of result buffer.
 * @param iface dbus inteface holding property.
 * @param name the name of the property to retrieve from dbus iface.
 * @return retuns 0 on success, -1 otherwise.
 *
 * @todo finish creating dBusMsgCall struct/class with 4-pointers for
 * the dest, path, iface, method parameters for dbus_message_new_method_call.
 */
int kgenericbacklightWidget::dbusGetProperty (QString &result,
                                            const char *iface, const char *name)
{
  DBusConnection *dbus_conn = NULL;       /* dbus connection */
  DBusMessage *dbus_msg = NULL;           /* dbus message */
  DBusMessage *dbus_reply = NULL;         /* dbus reply */
  DBusError dbus_error;                   /* dbus error */
  DBusMessageIter iter,                   /* iterator for dbus_reply */
                  sub;                    /* sub-iterator to recurse iter */
  char *dbus_result = NULL;               /* string from reply */
  int current_type;                       /* int to validate sub-iter type */

  /* initialize dbus_error */
  dbus_error_init (&dbus_error);

  /* connect to dbus */
  dbus_conn = dbus_bus_get (DBUS_BUS_SYSTEM, &dbus_error);

  /* create dbus message / validate */
  if ((dbus_msg = dbus_message_new_method_call ("org.freedesktop.login1",
                                  "/org/freedesktop/login1/session/auto",
                                  "org.freedesktop.DBus.Properties",
                                  "Get")) == NULL) {
    perror (dbus_error.name);
    perror (dbus_error.message);

    return -1;
  }

  /* append arguments to dbus message / validate */
  if (dbus_message_append_args (dbus_msg, DBUS_TYPE_STRING, &iface,
                                DBUS_TYPE_STRING, &name,
                                DBUS_TYPE_INVALID) == false) {
    return -1;
  }

  /* invoke dbus rpc, block for reply */
  if ((dbus_reply = dbus_connection_send_with_reply_and_block (dbus_conn,
                                dbus_msg,
                                DBUS_TIMEOUT_USE_DEFAULT,
                                &dbus_error)) == NULL) {
    perror (dbus_error.name);
    perror (dbus_error.message);

    return -1;
  }

  /* get result property */

  /* initialize iterator for DBUS_TYPE_VARIANT container */
  if (dbus_message_iter_init (dbus_reply, &iter) == false) {
    perror (dbus_error.name);
    perror (dbus_error.message);

    return -1;
  }

  /* initialize sub-iterator to recurse into variant container */
  dbus_message_iter_recurse (&iter, &sub);

  /* validate DBUS_TYPE_STRING signature for sub-iterator
   * (specific for this example, you would need to handle additional types)
   */
  if ((current_type = dbus_message_iter_get_arg_type (&sub)) !=
      DBUS_TYPE_STRING) {
    fprintf (stderr, "error current type: %d (DBUS_TYPE_STRING: %d)\n",
              current_type, DBUS_TYPE_STRING);

    return -1;
  }

  /* get basic type (string) from sub-iterator */
  dbus_message_iter_get_basic (&sub, &dbus_result);

  /* validate result not NULL */
  if (dbus_result == NULL) {
    fputs ("error: dbus_result NULL.\n", stderr);

    return -1;
  }

  result = dbus_result;

  /* unref reply, message and connection */
  dbus_message_unref (dbus_reply);
  dbus_message_unref (dbus_msg);
  dbus_connection_unref (dbus_conn);

  return 0;
}


/**
 * @brief slot function taking in value to set backlight brightness over
 * dbus allowing non-privileged users access to the sysfs.
 * @param value the value to set hardware display backlight to.
 * @return returns the backlight value set on success, -1 otherwise.
 */
int kgenericbacklightWidget::dbusSetBrightness ( int value )
{
  DBusConnection *dbus_conn = NULL;       /* dbus connection */
  DBusMessage *dbus_msg = NULL;           /* dbus message */
  DBusMessage *dbus_reply = NULL;         /* dbus reply */
  DBusError dbus_error;                   /* dbus error */

  uint32_t b = value;                     /* backlight_value */
  const char  *bl = "backlight",          /* subsystem: backlight */
              *av = sysfsbl->get_blIface().latin1(); /* name: acpi_video0 */

  /* validate value 0 <= value <= max_brightness */
  if (value < 0 || max_brightness < value) {
    qWarning ("error: value out of range (0 <= %d <= %d)",
              value, max_brightness);
    return -1;
  }

  /* initialize dbus_error */
  ::dbus_error_init(&dbus_error);

  /* connect to dbus */
  if ((dbus_conn = ::dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error)) == NULL) {
    qWarning ("error: dbus_bus_get connection failed.");
    ::perror (dbus_error.name);
    ::perror (dbus_error.message);

    return -1;
  }


  /* create dbus message / validate */
  if ((dbus_msg = ::dbus_message_new_method_call ("org.freedesktop.login1",
                                    "/org/freedesktop/login1/session/auto",
                                    "org.freedesktop.login1.Session",
                                    "SetBrightness")) == NULL) {
    qWarning ("error: dbus_message_new_method_call failed.");

    ::perror (dbus_error.name);
    ::perror (dbus_error.message);
    ::dbus_connection_unref (dbus_conn);

    return -1;
  }

  /* append arguments to dbus message / validate */
  if (::dbus_message_append_args (dbus_msg,
        DBUS_TYPE_STRING, &bl,
        DBUS_TYPE_STRING, &av,
        DBUS_TYPE_UINT32, &b,
        DBUS_TYPE_INVALID) == false) {
    qWarning ("error: dbus_message_append_args failed.");

            ::dbus_connection_unref(dbus_conn);
            ::dbus_message_unref (dbus_msg);

    return -1;
  }

  /* invoke dbus rpc, block for reply */
  if ((dbus_reply = ::dbus_connection_send_with_reply_and_block (dbus_conn,
      dbus_msg,
      DBUS_TIMEOUT_USE_DEFAULT,
      &dbus_error)) == NULL) {
    qWarning ("error: dbus_connection_send_with_reply_and_block failed.");

          ::perror (dbus_error.name);
          ::perror (dbus_error.message);
          ::dbus_connection_unref(dbus_conn);
          ::dbus_message_unref (dbus_msg);

    return -1;
  }

                  /* unref reply, message and connection */
  ::dbus_message_unref (dbus_reply);
  ::dbus_message_unref (dbus_msg);
  ::dbus_connection_unref (dbus_conn);

  return value;
}



#include "kgenericbacklightwidget.moc"

