/******************************************************************************#
#           guvcview              http://guvcview.sourceforge.net              #
#                                                                              #
#           Paulo Assis <pj.assis@gmail.com>                                   #
#                                                                              #
# 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 <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "core_io.h"
#include "gview.h"
#include "options.h"

#define API_HELP_GTK3_QT6 N_("Select GUI API (e.g none; gtk3; qt6)")
#define API_HELP_GTK3 N_("Select GUI API (e.g none; gtk3)")
#define API_HELP_QT6 N_("Select GUI API (e.g none; qt6)")
#define API_HELP_NONE N_("Select GUI API (e.g none)")

typedef struct _opt_values_t {
  char opt_short;
  char opt_long[20];
  int req_arg;
  char opt_help_arg[20];
  char opt_help[80];
} opt_values_t;

static opt_values_t opt_values[] = {
    {.opt_short = 'h',
     .opt_long = "help",
     .req_arg = 0,
     .opt_help_arg = "",
     .opt_help = N_("Print help")},
    {
        .opt_short = 'v',
        .opt_long = "version",
        .req_arg = 0,
        .opt_help_arg = "",
        .opt_help = N_("Print version"),
    },
    {.opt_short = 'w',
     .opt_long = "verbosity",
     .req_arg = 1,
     .opt_help_arg = N_("LEVEL"),
     .opt_help = N_("Set Verbosity level (def: 0)")},
    {
        .opt_short = 'd',
        .opt_long = "device",
        .req_arg = 1,
        .opt_help_arg = N_("DEVICE"),
        .opt_help = N_("Set device name (def: /dev/video0)"),
    },
    {
        .opt_short = 'c',
        .opt_long = "capture",
        .req_arg = 1,
        .opt_help_arg = N_("METHOD"),
        .opt_help = N_("Set capture method [read | mmap (def)]"),
    },
    {
        .opt_short = 'b',
        .opt_long = "disable_libv4l2",
        .req_arg = 0,
        .opt_help_arg = "",
        .opt_help = N_("disable calls to libv4l2"),
    },
    {.opt_short = 'x',
     .opt_long = "resolution",
     .req_arg = 1,
     .opt_help_arg = N_("WIDTHxHEIGHT"),
     .opt_help = N_("Request resolution (e.g 640x480)")},
    {.opt_short = 'f',
     .opt_long = "format",
     .req_arg = 1,
     .opt_help_arg = N_("FOURCC"),
     .opt_help = N_("Request format (e.g MJPG)")},
    {.opt_short = 'F',
     .opt_long = "fps",
     .req_arg = 1,
     .opt_help_arg = N_("FPS_NUM[/FPS_DENOM]"),
     .opt_help = N_("Request fps (e.g 25 ; 7/3; ...)")},
    {.opt_short = 'r',
     .opt_long = "render",
     .req_arg = 1,
     .opt_help_arg = N_("RENDER_API"),
     .opt_help = N_("Select render API (e.g none; sdl; sfml)")},
    {.opt_short = 'm',
     .opt_long = "render_window",
     .req_arg = 1,
     .opt_help_arg = N_("RENDER_WINDOW_FLAGS"),
     .opt_help =
         N_("Set render window flags (e.g none; full; max; WIDTHxHEIGHT)")},
    {.opt_short = 'a',
     .opt_long = "audio",
     .req_arg = 1,
     .opt_help_arg = N_("AUDIO_API"),
     .opt_help = N_("Select audio API (e.g none; port; pulse)")},
    {.opt_short = 'k',
     .opt_long = "audio_device",
     .req_arg = 1,
     .opt_help_arg = N_("AUDIO_DEVICE"),
     .opt_help = N_("Select audio device index for selected api (0..N)")},
    {.opt_short = 'g',
     .opt_long = "gui",
     .req_arg = 1,
     .opt_help_arg = N_("GUI_API"),
#if ENABLE_QT6 && ENABLE_GTK3
     .opt_help = API_HELP_GTK3_QT6
#elif ENABLE_GTK3
     .opt_help = API_HELP_GTK3
#elif ENABLE_QT6
     .opt_help = API_HELP_QT6
#else
     .opt_help = API_HELP_NONE
#endif

    },
    {.opt_short = 'o',
     .opt_long = "audio_codec",
     .req_arg = 1,
     .opt_help_arg = N_("CODEC"),
     .opt_help = N_("Audio codec [pcm mp2 mp3 aac ac3 vorb]")},
    {.opt_short = 'u',
     .opt_long = "video_codec",
     .req_arg = 1,
     .opt_help_arg = N_("CODEC"),
     .opt_help = N_("Video codec [raw mjpg mpeg flv1 wmv1 mpg2 mp43 dx50 h264 "
                    "vp80 theo]")},
    {.opt_short = 'p',
     .opt_long = "profile",
     .req_arg = 1,
     .opt_help_arg = N_("FILENAME"),
     .opt_help = N_("load control profile")},
    {.opt_short = 'j',
     .opt_long = "video",
     .req_arg = 1,
     .opt_help_arg = N_("FILENAME"),
     .opt_help = N_("filename for captured video)")},
    {.opt_short = 'i',
     .opt_long = "image",
     .req_arg = 1,
     .opt_help_arg = N_("FILENAME"),
     .opt_help = N_("filename for captured image)")},
    {.opt_short = 'y',
     .opt_long = "video_timer",
     .req_arg = 1,
     .opt_help_arg = N_("TIME_IN_SEC"),
     .opt_help = N_("time (double) in sec. for video capture)")},
    {.opt_short = 't',
     .opt_long = "photo_timer",
     .req_arg = 1,
     .opt_help_arg = N_("TIME_IN_SEC"),
     .opt_help = N_("time (double) in sec. between captured photos)")},
    {.opt_short = 'n',
     .opt_long = "photo_total",
     .req_arg = 1,
     .opt_help_arg = N_("TOTAL"),
     .opt_help = N_("total number of captured photos)")},
    {.opt_short = 'e',
     .opt_long = "exit_on_term",
     .req_arg = 0,
     .opt_help_arg = "",
     .opt_help = N_("exit app after video or image capture ends")},
    {.opt_short = 'z',
     .opt_long = "control_panel",
     .req_arg = 0,
     .opt_help_arg = "",
     .opt_help = N_("Start in control panel mode")},
    {.opt_short = 0,
     .opt_long = "",
     .req_arg = 0,
     .opt_help_arg = "",
     .opt_help = ""},
};

static options_t my_options = {.verbosity = 0,
                               .device = "/dev/video0",
                               .width = 0,
                               .height = 0,
                               .control_panel = 0,
                               .disable_libv4l2 = 0,
                               .format = "",
                               .render = "",
                               .gui = "",
                               .audio = "",
                               .audio_device = -1, /*use default*/
                               .capture = "",
                               .video_codec = "",
                               .audio_codec = "",
                               .prof_filename = NULL,
                               .profile_name = NULL,
                               .profile_path = NULL,
                               .video_name = NULL,
                               .video_path = NULL,
                               .photo_name = NULL,
                               .photo_path = NULL,
                               .video_timer = 0,
                               .photo_timer = 0,
                               .photo_npics = 0,
                               .exit_on_term = 0,
                               .render_flag = "none",
                               .render_width = 0,
                               .render_height = 0};

/*
 * get the internal options data
 * args:
 *   none
 *
 * asserts:
 *   none
 *
 * returns: pointer to internal options_t struct
 */
options_t *options_get() { return &my_options; }

/*
 * prints the number of command line options
 * args:
 *   none
 *
 * asserts:
 *   none
 *
 * returns: last valid index of opt_values
 */
int opt_get_number() {
  int i = 0;

  /*long option must always be set*/
  do {
    i++;
  } while (strlen(opt_values[i].opt_long) > 0);

  return i;
}

/*
 * gets the max length of help string (up to end of opt_help_arg)
 * args:
 *   none
 *
 * asserts:
 *   none
 *
 * returns: max lenght
 */
static int opt_get_help_max_len() {
  int i = 0;

  int max_len = 0;

  /*long option must always be set*/
  do {
    int len = 5 + /*-c, and --*/
              strlen(opt_values[i].opt_long);
    if (strlen(opt_values[i].opt_help_arg) > 0)
      len += strlen(opt_values[i].opt_help_arg) + 1; /*add =*/
    if (len > max_len)
      max_len = len;
    i++;
  } while (strlen(opt_values[i].opt_long) > 0);

  return max_len;
}

/*
 * prints the command line help
 * args:
 *   none
 *
 * asserts:
 *   none
 *
 * returns: none
 */
void opt_print_help() {
  printf(_("Guvcview version %s\n\n"), VERSION);
  printf(_("Usage:\n   guvcview [OPTIONS]\n\n"));
  printf(_("OPTIONS:\n"));

  int max_len = opt_get_help_max_len();
  int len = 0;

  int i = 0;

  /*long option must always be set*/
  do {
    if (opt_values[i].opt_short > 0) {
      len = 3;
      printf("-%c,", opt_values[i].opt_short);
    }

    printf("--%s", opt_values[i].opt_long);
    len += strlen(opt_values[i].opt_long) + 2;

    if (strlen(opt_values[i].opt_help_arg) > 0) {
      len += strlen(opt_values[i].opt_help_arg) + 1;
      printf("=%s", _(opt_values[i].opt_help_arg));
    }

    int spaces = max_len - len;
    int j = 0;
    for (j = 0; j < spaces; j++)
      printf(" ");

    if (strlen(opt_values[i].opt_help) > 0)
      printf("\t:%s\n", _(opt_values[i].opt_help));

    i++;
  } while (strlen(opt_values[i].opt_long) > 0);
}

/*
 * prints the version info
 * args:
 *   none
 *
 * asserts:
 *   none
 *
 * returns: none
 */
void opt_print_version() { printf("Guvcview version %s\n", VERSION); }

/*
 * parses the command line options
 * args:
 *   argc - number of comman line args
 *   argv - pointer to list of command line args
 *
 * asserts:
 *   none
 *
 * returns: int (if > 0 app should terminate (help, version)
 */
int options_parse(int argc, char *argv[]) {
  int ret = 0;
  int long_index = 0;
  char *stopstring;

  int n_options = opt_get_number();

  struct option long_options[n_options + 1];

  char opt_string[128] = "";
  char *opt_str_ptr = opt_string;

  int i = 0;
  for (i = 0; i < n_options; i++) {
    long_options[i].name = opt_values[i].opt_long;
    long_options[i].has_arg =
        opt_values[i].req_arg > 0 ? required_argument : no_argument;
    long_options[i].flag = NULL;
    long_options[i].val = opt_values[i].opt_short;

    /*set opt string (be carefull we don't exceed size)*/
    if (opt_str_ptr - opt_string < 128 - 3) {
      *opt_str_ptr++ = opt_values[i].opt_short;
      if (opt_values[i].req_arg > 0)
        *opt_str_ptr++ = ':';
    }
  }

  long_options[n_options].name = 0;
  long_options[n_options].has_arg = 0;
  long_options[n_options].flag = NULL;
  long_options[n_options].val = 0;

  *opt_str_ptr++ = '\0'; /*null terminated string*/

  int opt = 0;

  while ((opt = getopt_long(argc, argv, opt_string, long_options,
                            &long_index)) != -1) {
    switch (opt) {
    case 'v':
      opt_print_version();
      ret = 1;
      break;

    case 'w':
      my_options.verbosity = atoi(optarg);
      break;

    case 'd': {
      int str_size = strlen(optarg);
      if (str_size > 1) /*device needs at least 2 chars*/
        strncpy(my_options.device, optarg, 29);
      else
        fprintf(stderr, "V4L2_CORE: (options) Error in device usage: "
                        "-d[--device] DEVICENAME \n");
      break;
    }
    case 'z': {
      my_options.control_panel = 1;
      break;
    }
    case 'c': {
      int str_size = strlen(optarg);
      if (str_size == 4) /*capture method*/
        strncpy(my_options.capture, optarg, 4);
      break;
    }
    case 'b': {
      my_options.disable_libv4l2 = 1;
      break;
    }
    case 'x':
      my_options.width = (int)strtoul(optarg, &stopstring, 10);
      if (*stopstring != 'x') {
        fprintf(stderr, "V4L2_CORE: (options) Error in resolution usage: "
                        "-x[--resolution] WIDTHxHEIGHT \n");
      } else {
        ++stopstring;
        my_options.height = (int)strtoul(stopstring, &stopstring, 10);
      }
      if (my_options.width <= 0)
        my_options.width = 0;
      if (my_options.height <= 0)
        my_options.height = 0;
      break;

    case 'f': {
      int str_size = strlen(optarg);
      if (str_size == 4) /*fourcc is 4 chars*/
        strncpy(my_options.format, optarg, 4);
      break;
    }

    case 'F':
      /* numerator and denominator are reversed
       * since fps here is actually time between frame
       */
      my_options.fps_num = 1;
      my_options.fps_denom = (int)strtoul(optarg, &stopstring, 10);
      if (*stopstring == '/') {
        ++stopstring;
        my_options.fps_num = (int)strtoul(stopstring, &stopstring, 10);
      }
      if (my_options.fps_denom <= 0)
        my_options.fps_denom = 25;
      if (my_options.fps_num <= 0)
        my_options.fps_num = 1;
      break;

    case 'r': {
      int str_size = strlen(optarg);
      if (str_size <= 4) /*render is at most 4 chars*/
        strncpy(my_options.render, optarg, 4);
      break;
    }
    case 'm': {
      int str_size = strlen(optarg);
      if (str_size <= 4) /*[none, full, max] is at most 4 chars*/
        strncpy(my_options.render_flag, optarg, 4);
      else {
        my_options.render_width = (int)strtoul(optarg, &stopstring, 10);
        if (my_options.render_width <= 0 || *stopstring != 'x')
          fprintf(stderr, "V4L2_CORE: (options) Error in render_window usage: "
                          "-m[--render_window=] none|full|max|WIDTHxHEIGHT \n");
        else {
          ++stopstring;
          my_options.render_height = (int)strtoul(stopstring, &stopstring, 10);
        }
      }

      if (my_options.render_width < 0)
        my_options.render_width = 0;
      if (my_options.render_height < 0)
        my_options.render_height = 0;

      break;
    }
    case 'g': {
      int str_size = strlen(optarg);
      if (str_size <= 4) /*gui is at maximum 4 chars*/
        strncpy(my_options.gui, optarg, 4);
      break;
    }
    case 'a': {
      /*audio api is at most 5 chars (p u l s e)*/
      strncpy(my_options.audio, optarg, 5);
      break;
    }
    case 'k':
      my_options.audio_device = atoi(optarg);
      break;
    case 'o': {
      int str_size = strlen(optarg);
      if (str_size > 2) /*audio codec*/
        strncpy(my_options.audio_codec, optarg, 4);
      break;
    }
    case 'u': {
      int str_size = strlen(optarg);
      if (str_size > 2) /*video codec*/
        strncpy(my_options.video_codec, optarg, 4);
      break;
    }
    case 'p': {
      if (my_options.prof_filename != NULL)
        free(my_options.prof_filename);
      my_options.prof_filename = strdup(optarg);
      /*get profile path and basename*/
      char *basename = get_file_basename(optarg);
      if (basename) {
        // set_profile_name(basename);
        if (my_options.profile_name != NULL)
          free(my_options.profile_name);
        my_options.profile_name = basename;
      }
      char *pathname = get_file_pathname(optarg);
      if (pathname) {
        // set_profile_path(pathname);
        if (my_options.profile_path != NULL)
          free(my_options.profile_path);
        my_options.profile_path = pathname;
      }

      break;
    }
    case 'j': {
      /*get video path and basename*/
      char *basename = get_file_basename(optarg);
      if (basename) {
        // set_video_name(basename);
        // free(basename);
        if (my_options.video_name != NULL)
          free(my_options.video_name);
        my_options.video_name = basename;
      }
      char *pathname = get_file_pathname(optarg);
      if (pathname) {
        // set_video_path(pathname);
        // free(pathname);
        if (my_options.video_path != NULL)
          free(my_options.video_path);
        my_options.video_path = pathname;
      }

      break;
    }
    case 'i': {
      /*get photo path and basename*/
      char *basename = get_file_basename(optarg);
      if (basename) {
        // set_photo_name(basename);
        // free(basename);
        if (my_options.photo_name != NULL)
          free(my_options.photo_name);
        my_options.photo_name = basename;
      }
      char *pathname = get_file_pathname(optarg);
      if (pathname) {
        // set_photo_path(pathname);
        // free(pathname);
        if (my_options.photo_path != NULL)
          free(my_options.photo_path);
        my_options.photo_path = pathname;
      }

      break;
    }
    case 'y':
      my_options.video_timer = strtod(optarg, (char **)NULL);
      break;
    case 't':
      my_options.photo_timer = strtod(optarg, (char **)NULL);
      break;
    case 'n':
      my_options.photo_npics = atoi(optarg);
      break;
    case 'e':
      my_options.exit_on_term = 1;
      break;
    default:
    case 'h':
      opt_print_help();
      ret = 1;
      break;
    }
  }

  return ret;
}

/*
 * cleans internal options allocations
 * args:
 *    none
 *
 * asserts:
 *    none
 *
 * returns: none
 */
void options_clean() {
  if (my_options.prof_filename != NULL)
    free(my_options.prof_filename);
  my_options.prof_filename = NULL;

  if (my_options.profile_name != NULL)
    free(my_options.profile_name);
  my_options.profile_name = NULL;

  if (my_options.profile_path != NULL)
    free(my_options.profile_path);
  my_options.profile_path = NULL;

  if (my_options.video_name != NULL)
    free(my_options.video_name);
  my_options.video_name = NULL;

  if (my_options.video_path != NULL)
    free(my_options.video_path);
  my_options.video_path = NULL;

  if (my_options.photo_name != NULL)
    free(my_options.photo_name);
  my_options.photo_name = NULL;

  if (my_options.photo_path != NULL)
    free(my_options.photo_path);
  my_options.photo_path = NULL;
}
