/*
 This file is part of TALER
 Copyright (C) 2024 Taler Systems SA

 TALER 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 3, or (at your option) any later version.

 TALER 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
 TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file taler-mdb-display.c
 * @brief turn on the display back light on or off
 * @author Boss Marco
 * @author Christian Grothoff
 */
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <gnunet/gnunet_util_lib.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <fcntl.h>
/* for adafruit pitft display */
#include <linux/fb.h>
#include "taler_mdb_util.h"

/**
 * Handle for the Framebuffer device
 */
struct Display
{
  /**
   * File descriptor for the screen
   */
  int devicefd;

  /**
   * File descriptor to set backlight information
   */
  int backlightfd;

  /**
   * The display memory to set the pixel information
   */
  uint16_t *memory;

  /**
   * Original screen information
   */
  struct fb_var_screeninfo orig_vinfo;

  /**
   * Variable screen information (color depth ...)
   */
  struct fb_var_screeninfo var_info;

  /**
   * Fixed screen informtaion
   */
  struct fb_fix_screeninfo fix_info;
};


/**
 * Name of the framebuffer device (i.e. /dev/fb1).
 */
static char *framebuffer_device_filename;

/**
 * Name of the backlight file of @e framebuffer_device_filename (i.e. /sys/class/backlight/soc:backlight/brightness).
 */
static char *framebuffer_backlight_filename;

/**
 * Global option '-i' to invert backlight on/off values
 */
static int backlight_invert;

/**
 * Standard backlight on value
 */
static char backlight_on = '1';

/**
 * Standard backlight off value
 */
static char backlight_off = '0';

/**
 * Handle for the framebuffer device
 */
static struct Display qrDisplay;


static void
shutdown_task (void *cls)
{
  (void) cls;
  if (NULL != qrDisplay.memory)
    memset (qrDisplay.memory,
            0xFF,
            qrDisplay.var_info.xres * qrDisplay.var_info.yres
            * sizeof (uint16_t));
  if (NULL != qrDisplay.memory)
  {
    /* free the display data  */
    munmap (qrDisplay.memory,
            qrDisplay.fix_info.smem_len);
    qrDisplay.memory = NULL;
    /* close device */
    GNUNET_break (0 == close (qrDisplay.devicefd));
    qrDisplay.devicefd = -1;
    if (0 < qrDisplay.backlightfd)
      GNUNET_break (0 == close (qrDisplay.backlightfd));
    qrDisplay.backlightfd = -1;
  }
}


/**
 * @brief Start the application
 *
 * @param cls closure
 * @param args arguments left
 * @param cfgfile config file name
 * @param cfg handle for the configuration file
 */
static void
run (void *cls,
     char *const *args,
     const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  (void) cls;
  (void) cfgfile;

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_filename (cfg,
                                               "taler-mdb",
                                               "FRAMEBUFFER_DEVICE",
                                               &framebuffer_device_filename))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "taler-mdb",
                               "FRAMEBUFFER_DEVICE");
    framebuffer_device_filename = GNUNET_strdup ("/dev/fb1");
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_filename (cfg,
                                               "taler-mdb",
                                               "FRAMEBUFFER_BACKLIGHT",
                                               &framebuffer_backlight_filename))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "taler-mdb",
                               "FRAMEBUFFER_BACKLIGHT");
    framebuffer_backlight_filename = GNUNET_strdup (
      "/sys/class/backlight/soc:backlight/brightness");
  }
  /* open the framebuffer device */
  qrDisplay.devicefd = open (framebuffer_device_filename,
                             O_RDWR);
  if (0 < qrDisplay.devicefd)
  {
    /* read information about the screen */
    ioctl (qrDisplay.devicefd,
           FBIOGET_VSCREENINFO,
           &qrDisplay.var_info);

    /* store current screeninfo for reset */
    qrDisplay.orig_vinfo = qrDisplay.var_info;

    if (16 != qrDisplay.var_info.bits_per_pixel)
    {
      /* Change variable info to 16 bit per pixel */
      qrDisplay.var_info.bits_per_pixel = 16;
      if (0 > ioctl (qrDisplay.devicefd,
                     FBIOPUT_VSCREENINFO,
                     &qrDisplay.var_info))
      {
        GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
                             "ioctl(FBIOPUT_VSCREENINFO)");
        return;
      }
    }

    /* Get fixed screen information */
    if (0 > ioctl (qrDisplay.devicefd,
                   FBIOGET_FSCREENINFO,
                   &qrDisplay.fix_info))
    {
      GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
                           "ioctl(FBIOGET_FSCREENINFO)");
      return;
    }

    /* get pointer onto frame buffer */
    qrDisplay.memory =  mmap (NULL,
                              qrDisplay.fix_info.smem_len,
                              PROT_READ | PROT_WRITE, MAP_SHARED,
                              qrDisplay.devicefd,
                              0);

    /* open backlight file to turn display backlight on and off */
    if (0 > qrDisplay.devicefd)
    {
      GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
                           "mmap");
      return;
    }

    memset (qrDisplay.memory,
            0xFF,
            qrDisplay.var_info.xres * qrDisplay.var_info.yres
            * sizeof (uint16_t));

    qrDisplay.backlightfd = open (
      framebuffer_backlight_filename, O_WRONLY);
    if (0 > qrDisplay.backlightfd)
    {
      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
                                "open",
                                framebuffer_backlight_filename);
    }
    else
    {
      if (backlight_invert)
      {
        backlight_on = '0';
        backlight_off = '1';
      }
      (void) ! write (qrDisplay.backlightfd,
                      &backlight_off,
                      1);
    }
  }
  else
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
                              "open",
                              framebuffer_device_filename);
  }
  GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
                                 NULL);

  if (0 < qrDisplay.backlightfd)
    (void) ! write (qrDisplay.backlightfd,
                    &backlight_on,
                    1);
}


int
main (int argc,
      char*const*argv)
{
  enum GNUNET_GenericReturnValue ret;
  struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_option_flag ('i',
                               "backlight-invert",
                               "toggle on vs. off",
                               &backlight_invert),
    GNUNET_GETOPT_OPTION_END
  };

  ret = GNUNET_PROGRAM_run (TALER_MDB_project_data (),
                            argc,
                            argv,
                            "taler-mdb-display",
                            "Turn frame buffer display on or off\n",
                            options,
                            &run,
                            NULL);
  if (GNUNET_OK != ret)
    return 1;
  return 0;
}
