/* Simple wrapper to the external program `zenity' (1) (display GTK+ dialogs)
   Copyright (C) 2014  Jean-Vincent Loddo
   GNU General Public License, see <http://www.gnu.org/licenses/>. */

/* Test with: gcc -o zenity_wrap zenity_wrap.c */

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

typedef char * string;

//////////////////////////////////////////////////// A P I //////////////////////////////////////////////////////////

/* Basic dialogs: */

void    zenity_info      (string text);
void    zenity_warning   (string text);
void    zenity_error     (string text);

bool    zenity_question  (string text);
string  zenity_entry     (string text);
int     zenity_scale     (string text, int value, int min_value, int max_value, int step, int on_cancel);
string  zenity_calendar  (string text);
string  zenity_radiolist (string text, string header1, string header2, int arg_no, ...);
string  zenity_checklist (string text, string header1, string header2, bool checkall, string separator, int arg_no, ...);

/* --- File and directory selection: */

string  zenity_file_selection                (bool save, bool confirm_overwrite, string file_filter);
string  zenity_file_selection_multiple       (string separator, string file_filter);
string  zenity_directory_selection           (string file_filter);
string  zenity_directory_selection_multiple  (string separator, string file_filter);

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/* In order to understand the precise meaning of a function result: */
typedef char *   fresh_string;
typedef char * * fresh_string_array;
typedef char * * string_array;

/**/ fresh_string /**/ malloc_strcpy(string source) {
  string result = malloc(strlen(source)+1);
  strcpy(result, source);
  return(result);
}

/* Example:
  void  my_variadic_function (int arg_no, ...) {
    MAKE_ARRAY_FROM_ARGUMENTS(string, arg_no, xs)
    // ...
    // body working on the string array xs
    // ...
  } 
*/
#define MAKE_ARRAY_FROM_ARGUMENTS(TYPE, ARG_NO, ARRAY_NAME) \
  va_list ap; \
  va_start(ap, ARG_NO); \
  TYPE * ARRAY_NAME = malloc(ARG_NO * sizeof(TYPE)); \
  { register int i; for(i = 0; i < ARG_NO; i++) ARRAY_NAME[i] = va_arg(ap, TYPE); } \
  va_end(ap);

// Make an array of strings from a variadic call: 
/**/ fresh_string_array /**/ malloc_string_array_of (int arg_no, ...) {
  MAKE_ARRAY_FROM_ARGUMENTS(string, arg_no, xs)
  return xs;
}

/**/ fresh_string /**/ malloc_string_concat (int xs_size, string_array xs) {
  int i, total_length=0;
  for (i=0; i<xs_size; i++) total_length += strlen(xs[i]);
  //---
  fresh_string result = malloc(total_length+1);
  //---
  int offset=0;
  for (i=0; i<xs_size; i++) { 
    strcpy(result+offset, xs[i]);
    offset += strlen(xs[i]);
    }
  //---
  result[offset]='\0';
  return result;
}

/* Variadic version: */
/**/ fresh_string /**/ malloc_string_concat_variadic (int arg_no, ...) {
  MAKE_ARRAY_FROM_ARGUMENTS(string, arg_no, xs)
  return(malloc_string_concat(arg_no, xs));  
}

/* Usage: malloc_mktemp("/tmp/mycallback.XXXXXX"); */
/**/ fresh_string /**/ malloc_mktemp (const char * template) { 
  char * TEMPLATE =  malloc (strlen(template)+1); 
  strcpy(TEMPLATE, template);
  return (mktemp(TEMPLATE));
}

int exit_code_of_status(int status) {
  if WIFEXITED(status) return(WEXITSTATUS(status)); else return(127) ;
}

bool bool_of_exit_code(int exit_code) {
 if (exit_code == 0) return true; else return false;  
}

int exit_code_of_bool(int exit_code) { 
  return(!exit_code);
}

bool bool_of_status(int status) {
  int exit_code = exit_code_of_status(status);
  int result    = bool_of_exit_code(exit_code);
  return(result);
}

bool zenity_question(const string text) {
  const string prefix = "zenity --question --text=\"";
  const string suffix = "\"";
  // ---  
  string command = malloc_string_concat(3, malloc_string_array_of(3, prefix, text, suffix));
  // ---  
  int status = system(command);
  int result = bool_of_status(status);
  free(command);
  return(result);
}

void PRIVATE_zenity_info_warning_or_error(const string kind, const string text) {
  const string prefix = "zenity --text=\"";
  const string suffix = "\" ";
  // ---  
  string command = malloc_string_concat(4, malloc_string_array_of(4, prefix, text, suffix, kind));
  // ---  
  system(command);
  free(command);
}

void zenity_info(const string text)    { PRIVATE_zenity_info_warning_or_error("--info", text);   }
void zenity_error(const string text)   { PRIVATE_zenity_info_warning_or_error("--error", text);  }
void zenity_warning(const string text) { PRIVATE_zenity_info_warning_or_error("--warning", text);}

/* No more then 1Kb (otherwise the content is truncated) */    
fresh_string malloc_content_of_at_most_1k_file (const string filename) { 
  FILE * fp = fopen (filename, "r");
  if (fp == NULL) return NULL;
  char buffer [1024];
  int k = fread(buffer, 1, 1023, fp);
  buffer[k]='\0';
  fclose(fp);
  string result = malloc(k+1);
  strcpy(result, buffer);
  return(result);
}

void string_remove_trailing_newline (string x) {
  int last = strlen(x) - 1 ;
  if (x[last] == '\n') x[last]='\0';
}

fresh_string PRIVATE_zenity_calendar_or_entry(const string kind, const string text) {
  const string prefix  = "zenity --text=\"";
  const string suffix  = "\" 1> ";
  const string tmpfile = malloc_mktemp("/tmp/zenity_wrap.XXXXXX");
  // ---  
  string command = malloc_string_concat(6, malloc_string_array_of(6, prefix, text, suffix, tmpfile, " ", kind));
  // ---  
  system(command);
  string result = malloc_content_of_at_most_1k_file(tmpfile);
  free(command);
  unlink(tmpfile);
  free(tmpfile);
  string_remove_trailing_newline(result);
  return(result);
}

fresh_string zenity_calendar (const string text) { return(PRIVATE_zenity_calendar_or_entry("--calendar", text)); }
fresh_string zenity_entry    (const string text) { return(PRIVATE_zenity_calendar_or_entry("--entry"   , text)); }

/* Options de la glissière :
   --text=TEXTE                                         Définit le texte de la boîte de dialogue
   --value=VALEUR                                       Définit la valeur initiale
   --min-value=VALEUR                                   Définit la valeur minimale
   --max-value=VALEUR                                   Définit la valeur maximale
   --step=VALEUR                                        Définit le pas
*/
int zenity_scale(const string text, int value, int min_value, int max_value, int step, int on_cancel) {
  char prefix [256];
  sprintf(prefix, "zenity --scale --value=%d --min-value=%d --max-value=%d --step=%d --text=\"", value, min_value, max_value, step);
  const string suffix  = "\" 1> ";
  const string tmpfile = malloc_mktemp("/tmp/zenity_wrap.XXXXXX");
  // ---  
  string command = malloc_string_concat(4, malloc_string_array_of(4, prefix, text, suffix, tmpfile));
  // ---  
  int status = system(command);
  bool success = bool_of_status(status);
  int result = on_cancel;
  if (success) {
    string content = malloc_content_of_at_most_1k_file(tmpfile);
    string_remove_trailing_newline(content);
    result = atoi(content);
  }
  free(command);
  unlink(tmpfile);
  free(tmpfile);
  return(result);
}
  
/**/ fresh_string /**/ malloc_zenity_fresh_file_filter(string file_filter) {
  if (file_filter == NULL) file_filter = malloc_strcpy("");
  if (strcmp(file_filter,"") != 0) {
    string buffer = malloc(24 + strlen(file_filter));
    sprintf(buffer,"--file-filter \"%s\"", file_filter);
    file_filter = buffer;
    }
  return(file_filter);
}

/**/ fresh_string /**/ malloc_zenity_fresh_separator(string separator) {
  if (separator == NULL) separator = malloc_strcpy("");
  if (strcmp(separator,"") != 0) {
    string buffer = malloc(24 + strlen(separator));
    sprintf(buffer,"--separator \"%s\"", separator);
    separator = buffer;
    }
  return(separator);
}

/* Options de sélection de fichiers
  --filename=NOM_DU_FICHIER                            Définit le nom du fichier
  --multiple                                           Autorise la sélection de plusieurs fichiers
  --directory                                          Active uniquement la sélection des répertoires
  --save                                               Active le mode de sauvegarde
  --separator=SÉPARATEUR                               Définit le caractère séparateur de sortie
  --confirm-overwrite                                  Confirme la sélection du fichier si le nom du fichier existe déjà
  --file-filter=NOM | MODÈLE1 MODÈLE2 ...              Définit un filtre de nom de fichiers
*/
fresh_string zenity_file_selection(bool save, bool confirm_overwrite, string file_filter) {
  char prefix [256];
  if (! save) confirm_overwrite=false; 
  file_filter = malloc_zenity_fresh_file_filter(file_filter);
  sprintf(prefix, "zenity --title \"File selection\" --file-selection %s %s %s", (save ? "--save" : ""), (confirm_overwrite ? "--confirm-overwrite" : ""), file_filter);
  const string suffix  = " 1> ";
  const string tmpfile = malloc_mktemp("/tmp/zenity_wrap.XXXXXX");
  // ---  
  string command = malloc_string_concat(3, malloc_string_array_of(3, prefix, suffix, tmpfile));
  // ---  
  system(command);
  string result = malloc_content_of_at_most_1k_file(tmpfile);
  free(command);
  unlink(tmpfile);
  free(tmpfile);
  string_remove_trailing_newline(result);
  return(result);
}

fresh_string zenity_directory_selection(string file_filter) {
  char prefix [256];
  file_filter = malloc_zenity_fresh_file_filter(file_filter);
  sprintf(prefix, "zenity --title \"Directory selection\" --file-selection %s --directory", file_filter);
  const string suffix  = " 1> ";
  const string tmpfile = malloc_mktemp("/tmp/zenity_wrap.XXXXXX");
  // ---  
  string command = malloc_string_concat(3, malloc_string_array_of(3, prefix, suffix, tmpfile));
  // ---  
  system(command);
  string result = malloc_content_of_at_most_1k_file(tmpfile);
  free(command);
  unlink(tmpfile);
  free(tmpfile);
  string_remove_trailing_newline(result);
  return(result);
}

fresh_string PRIVATE_zenity_file_selection_multiple(bool directory, string separator, string file_filter) {
  char prefix [256];
  file_filter = malloc_zenity_fresh_file_filter(file_filter);
  separator   = malloc_zenity_fresh_separator(separator);
  if (directory)
   sprintf(prefix, "zenity --title \"Directory(ies) selection\" --file-selection --directory --multiple %s %s", separator, file_filter);
  else
   sprintf(prefix, "zenity --title \"File(s) selection\" --file-selection --multiple %s %s", separator, file_filter);
  const string suffix  = " 1> ";
  const string tmpfile = malloc_mktemp("/tmp/zenity_wrap.XXXXXX");
  // ---  
  string command = malloc_string_concat(3, malloc_string_array_of(3, prefix, suffix, tmpfile));
  // printf("DEBUGGING: command='%s'\n",command);
  // ---  
  system(command);
  string result = malloc_content_of_at_most_1k_file(tmpfile);
  free(command);
  unlink(tmpfile);
  free(tmpfile);
  string_remove_trailing_newline(result);
  return(result);
}

/**/ fresh_string /**/ zenity_file_selection_multiple(string separator, string file_filter) {
 return(PRIVATE_zenity_file_selection_multiple(false, separator, file_filter));
}

/**/ fresh_string /**/ zenity_directory_selection_multiple(string separator, string file_filter) {
 return(PRIVATE_zenity_file_selection_multiple(true, separator, file_filter));
}

// zenity  --list --text="..." --radiolist --column "AAA" --column "BBB" TRUE aaa FALSE bbb
fresh_string zenity_radiolist(const string text, const string header1, const string header2, int arg_no, ...) {
  MAKE_ARRAY_FROM_ARGUMENTS(string, arg_no, xs)
  string_array ys = malloc(arg_no * sizeof(string));
  register int i;
  if (arg_no > 0) ys[0] = malloc_string_concat_variadic(3, "TRUE \"", xs[0], "\" ");
  for(i = 1; i < arg_no; i++) ys[i] = malloc_string_concat_variadic(3, "FALSE \"", xs[i], "\" ");
  const string trailer = malloc_string_concat(arg_no, ys);
  // ---  
  const string prefix  = "zenity --list --radiolist --text=\"";
  const string suffix  = "\" 1> ";
  const string tmpfile = malloc_mktemp("/tmp/zenity_wrap.XXXXXX");
  // ---  
  string command = 
    malloc_string_concat(10, 
       malloc_string_array_of(10, prefix, text, suffix, tmpfile, " --column \"", header1, "\" --column \"", header2, "\" ", trailer));
  // ---  
  // printf("DEBUGGING: command: %s\n",command);
  system(command);
  string result = malloc_content_of_at_most_1k_file(tmpfile);
  free(command);
  unlink(tmpfile);
  free(tmpfile);
  for(i = 0; i < arg_no; i++) free(ys[i]); // xs has been filled with actuals
  free(trailer); free(ys); free(xs);
  string_remove_trailing_newline(result);
  return(result);
}

// zenity  --list --text="..." --checklist --column "AAA" --column "BBB" TRUE aaa FALSE bbb
fresh_string zenity_checklist(const string text, const string header1, const string header2, bool checkall, string separator, int arg_no, ...) {
  MAKE_ARRAY_FROM_ARGUMENTS(string, arg_no, xs)
  string_array ys = malloc(arg_no * sizeof(string));
  register int i;
  string check = (checkall) ? "TRUE" : "FALSE";
  for(i = 0; i < arg_no; i++) ys[i] = malloc_string_concat_variadic(4, check, " \"", xs[i], "\" ");
  const string trailer = malloc_string_concat(arg_no, ys);
  // ---  
  separator = malloc_zenity_fresh_separator(separator);
  // ---  
  const string prefix  = "zenity --list --checklist --separator ':' --text=\"";
  const string suffix  = "\" 1> ";
  const string tmpfile = malloc_mktemp("/tmp/zenity_wrap.XXXXXX");
  // ---  
  string command = 
    malloc_string_concat(12, 
       malloc_string_array_of(12, 
          prefix, text, suffix, tmpfile, " ", separator, " --column \"", header1, "\" --column \"", header2, "\" ", trailer    // 12!
          ));
  // ---  
  // printf("DEBUGGING: command: %s\n",command);
  system(command);
  string result = malloc_content_of_at_most_1k_file(tmpfile);
  free(command);
  unlink(tmpfile);
  free(tmpfile);
  for(i = 0; i < arg_no; i++) free(ys[i]); // xs has been filled with actuals
  free(trailer); free(ys); free(xs);
  string_remove_trailing_newline(result);
  return(result);
}

/* Examples: */
/*
int main(int argc, char *argv[]) {

  string message = "Damn! you completely forgot!" ;
  
  zenity_info(message);
  zenity_warning(message);
  zenity_error(message);

  bool b = zenity_question("It's ok?");
  printf("b=%d\n", b);

  string name = zenity_entry("Your name?");
  printf("name=\"%s\"\n", name);

  // int zenity_scale (string text, int value, int min_value, int max_value, int step, int on_cancel);
  int age = zenity_scale("Your age?", 16, 0, 130, 1, 42);
  printf("x=%d\n", age);

  string date = zenity_calendar("When?");
  printf("date='%s'\n", date);

  // string zenity_radiolist (string text, string header1, string header2, int arg_no, ...);
  string choice  = zenity_radiolist("Fruit", "Click here", "Description", 3, "orange", "lemon", "banana");
  printf("choice=\"%s\"\n", choice);

  // string zenity_checklist (string text, string header1, string header2, bool checkall, string separator, int arg_no, ...);
  string choices = zenity_checklist("Fruit", "Click here", "Description", true, ":", 3, "orange", "lemon", "banana");
  printf("choices=\"%s\"\n", choices);
  
  // string zenity_file_selection (bool save, bool confirm_overwrite, string file_filter);
  string filename = zenity_file_selection(true, true, "*.c");
  printf("filename=\"%s\"\n", filename);

  // string zenity_file_selection_multiple (string separator, string file_filter);
  string filenames = zenity_file_selection_multiple(":", "*.c");
  printf("filenames=\"%s\"\n", filenames);

  // string zenity_directory_selection (string file_filter);
  string dir = zenity_directory_selection("*A*");  // Filter files containing the letter 'A'
  printf("dir=\"%s\"\n", dir);
  
  // string zenity_directory_selection_multiple (string separator, string file_filter);
  fresh_string dirs = zenity_directory_selection_multiple(" ","*A*");
  printf("dirs=\"%s\"\n", dirs);

  //---  
  return(exit_code_of_bool(b));
}
*/
