
/**
 * @file
 * This library interfaces with the standalone GUIs by formatting, writing and
 * reading structures describing widgets style and pairs variable/value. On the
 * FORTRAN SIC side, there exist only a table of pointers to fixed-length
 * strings containing the initial and returned value of a variable.  It is also
 * the role of this library to map C null-terminated strings used by the GUIs to
 * fixed-lengthstrings handy in
 * FORTRAN.
 */

#include "gpanel.h"

#include "ggui-message-c.h"
#include "gsys/gag_trace.h"
#include "gcore/gcomm.h"
#include "gcore/glaunch.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/file.h>
#include <sys/wait.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

#ifndef SIGCLD
#define SIGCLD SIGCHLD
#endif /* SIGCLD */

static sic_task_t _sic_task_null = SIC_TASK_NULL;

static char window_title[TITLELENGTH], helpfilename[HLPFILELNGTH];

static global_struct widgets[NSTRUCT];
/* curr_widget points to the current (last in use) widget in the array of widgets (first one at start) */
static global_struct *curr_widget = widgets;
/* last_widget points to the first widget overflowing the array of widgets */
static global_struct *last_widget = &widgets[NSTRUCT];

static int s_begin_group = 0;

static void my_f2c_strncpy( char *dest, CFC_FString txt, size_t fsize, size_t max)
{
    /* this routine should read a FORTRAN character array, i.e, not necessary
       null-terminated, of size 'fsize', and strncpy it null-terminated to
       the dest C string (of size 'max' ), BUT with all the trailing blanks
       removed (i.e., the \0 is writ before the trailing blanks). 'fsize'
       must be either the real length of the fortran char array, in that case
       it is defined in sic_structures.h and must be set accordingly in
       xgag.inc on the fortran side, or a smaller length, LENC(), passed by
       the fortran */
    size_t n = (fsize < max) ? fsize : max - 1;

    CFC_f2c_strcpy( dest, txt, (int)n);
}

/*
 * Same as my_f2c_strncpy, but with fortran string passed as C pointer
 */
static void my_fp2c_strncpy( char *dest, char *txt, size_t fsize, size_t max)
{
    CFC_FString tmp;

    CFC_STRING_VALUE( tmp) = txt;
    my_f2c_strncpy( dest, tmp, fsize, max);
}

static void my_c2fp_strcpy( char *dest, char *txt, size_t fsize)
{
    CFC_FString tmp;

    CFC_STRING_VALUE( tmp) = dest;
    CFC_c2f_strcpy( tmp, fsize, txt);
}

static SicCallbackHelp s_sic_callback_help = NULL;

void CFC_API xgag_set_callback_help( SicCallbackHelp fct)
{
    s_sic_callback_help = fct;
}

void build_help_variable(const char *help_file, const char *variable_name,
    char *output_text)
{

  CFC_DECLARE_LOCAL_STRING(f_help_file);
  CFC_DECLARE_LOCAL_STRING(f_variable_name);
  CFC_DECLARE_LOCAL_STRING(f_output_text);
  char c_output_text[HLPTEXTLNGTH+1];

  if (s_sic_callback_help == NULL) {
    strcpy( output_text, "Internal error: help function is not declared");
    return;
  }

  CFC_STRING_VALUE(f_help_file) = (char *)help_file;  /* Make Fortran strings pointing to C ones. */
  CFC_STRING_LENGTH(f_help_file) = strlen(help_file);

  if (variable_name == NULL) {
    CFC_STRING_VALUE(f_variable_name) = "";  /* Make Fortran strings pointing to C ones. */
    CFC_STRING_LENGTH(f_variable_name) = 0;
  } else {
    CFC_STRING_VALUE(f_variable_name) = (char *)variable_name;  /* Make Fortran strings pointing to C ones. */
    CFC_STRING_LENGTH(f_variable_name) = strlen(variable_name);
  }

  CFC_STRING_VALUE(f_output_text) = (char *)c_output_text;  /* Make Fortran strings pointing to C ones. */
  CFC_STRING_LENGTH(f_output_text) = HLPTEXTLNGTH;

  s_sic_callback_help(f_help_file, f_variable_name, f_output_text
    CFC_PASS_STRING_LENGTH(f_help_file) CFC_PASS_STRING_LENGTH(f_variable_name) CFC_PASS_STRING_LENGTH(f_output_text) );

  CFC_f2c_strcpy( output_text, c_output_text, HLPTEXTLNGTH);

}


void CFC_API xgag_separator( int *win_id)
{
    if (curr_widget >= last_widget) {
      ggui_c_message(seve_e, "SIC", "Widget structure is exhausted");
      return;
    }
    generic_struct *generic = &(curr_widget++)->generic;

    generic->window_id = *win_id;
    generic->type = SEPARATOR;
}

static global_struct *set_generic( int type, int win_id,
                                   CFC_FString variable, CFC_FString label
                                   CFC_DECLARE_STRING_LENGTH(variable)
                                   CFC_DECLARE_STRING_LENGTH(label) )
{
    global_struct *widget;
    generic_struct *gene;
    char variable_name[SICVLN];

    if (curr_widget >= last_widget) {
      ggui_c_message(seve_e, "SIC", "Widget structure is exhausted");
      return NULL;
    }

    my_f2c_strncpy( variable_name, variable, CFC_STRING_LENGTH( variable), SICVLN);
    for (widget = widgets; widget < curr_widget; widget++) {
        gene = &widget->generic;
        if (gene->type == type && !strcmp( gene->variable, variable_name) && gene->window_id == win_id)
            return widget;
    }                                  /* do not replicate widget */
    gene = &curr_widget->generic;
    gene->window_id = win_id;
    gene->type = type;
    strcpy( gene->variable, variable_name);
    my_f2c_strncpy( gene->label, label, CFC_STRING_LENGTH( label), LABELLENGTH);
    return curr_widget++;
}

void CFC_API xgag_logic( CFC_FString variable, CFC_FString txt, int *flag, int *win_id
                         CFC_DECLARE_STRING_LENGTH(variable)
                         CFC_DECLARE_STRING_LENGTH(txt) )
{
    logic_struct *logic = &set_generic( LOGIC, *win_id,
                                        variable, txt
                                        CFC_PASS_STRING_LENGTH(variable)
                                        CFC_PASS_STRING_LENGTH(txt) )->logic;

    if (logic) {
        logic->logic = flag;
        logic->userlogic = (*flag == 1);
    }
}

void CFC_API xgag_file( CFC_FString variable, CFC_FString txt, char *c, int *length,
                        CFC_FString filter, int *win_id
                        CFC_DECLARE_STRING_LENGTH(variable)
                        CFC_DECLARE_STRING_LENGTH(txt)
                        CFC_DECLARE_STRING_LENGTH(filter) )
{
    file_struct *file = &set_generic( BROWSER, *win_id,
                                      variable, txt
                                      CFC_PASS_STRING_LENGTH(variable)
                                      CFC_PASS_STRING_LENGTH(txt) )->file;

    if (file) {
        my_f2c_strncpy( file->filter, filter, CFC_STRING_LENGTH( filter), FILTERLENGTH);
        file->chain = c;
        my_fp2c_strncpy( file->userchain, c, *length, CHAINLENGTH);
        file->length = strlen( file->userchain);
    }
}


void CFC_API xgag_choice( CFC_FString variable, CFC_FString txt, char *ch, int *length,
                          xgag_choicelst * liste, int *nch, int *ind, int *win_id
                          CFC_DECLARE_STRING_LENGTH(variable)
                          CFC_DECLARE_STRING_LENGTH(txt) )
{
    int i;
    choice_struct *choice = &set_generic( CHOICE, *win_id,
                                          variable, txt
                                          CFC_PASS_STRING_LENGTH(variable)
                                          CFC_PASS_STRING_LENGTH(txt) )->choice;

    if (choice) {
        choice->choice = ch;
        my_fp2c_strncpy( choice->userchoice, ch, *length, CHAINLENGTH);
        choice->length = strlen( choice->userchoice);
        choice->nchoices = *nch;
        for (i = 0; i < choice->nchoices; i++) {
            my_fp2c_strncpy( choice->choices[i], liste[i].chs, X_CHOLEN, X_CHOLEN);
        }
        choice->mode = *ind;
    }
}

void CFC_API xgag_slider( CFC_FString variable, CFC_FString txt, double *f,
                          double *min, double *max, int *win_id
                          CFC_DECLARE_STRING_LENGTH(variable)
                          CFC_DECLARE_STRING_LENGTH(txt) )
{
    slider_struct *slider = &set_generic( SLIDER, *win_id,
                                          variable, txt
                                          CFC_PASS_STRING_LENGTH(variable)
                                          CFC_PASS_STRING_LENGTH(txt) )->slider;

    if (slider) {
        slider->value = f;
        slider->uservalue = *f;
        slider->width = *max - *min;
        slider->min = *min;
    }
}

void CFC_API xgag_ch( CFC_FString variable, CFC_FString txt, char *c, int *length,
                      int *win_id, int *editable
                      CFC_DECLARE_STRING_LENGTH(variable)
                      CFC_DECLARE_STRING_LENGTH(txt) )
{
    chain_struct *chain = &set_generic( CHAIN, *win_id,
                                        variable, txt
                                        CFC_PASS_STRING_LENGTH(variable)
                                        CFC_PASS_STRING_LENGTH(txt) )->chain;

    if (chain) {
        chain->chain = c;
        my_fp2c_strncpy( chain->userchain, c, *length, CHAINLENGTH);
        chain->length = strlen( chain->userchain);
        chain->editable = *editable;
    }
}

void CFC_API xgag_show( char *c, int *length, int *win_id)
{
    if (curr_widget >= last_widget) {
      ggui_c_message(seve_e, "SIC", "Widget structure is exhausted");
      return;
    }
    show_struct *show = &(curr_widget++)->show;

    show->window_id = *win_id;
    show->type = SHOW;
    my_fp2c_strncpy( show->text, c, *length, TEXTLENGTH);
}

void CFC_API xgag_button( CFC_FString title, CFC_FString command, CFC_FString label,
                          int *length, CFC_FString helptxt, CFC_FString moretxt
                          CFC_DECLARE_STRING_LENGTH(title)
                          CFC_DECLARE_STRING_LENGTH(command)
                          CFC_DECLARE_STRING_LENGTH(label)
                          CFC_DECLARE_STRING_LENGTH(helptxt)
                          CFC_DECLARE_STRING_LENGTH(moretxt) )
{
    if (curr_widget >= last_widget) {
      ggui_c_message(seve_e, "SIC", "Widget structure is exhausted");
      return;
    }
    button_struct *button = &(curr_widget++)->button;

    button->window_id = 0;
    button->type = BUTTON;
    my_f2c_strncpy( button->title, title, CFC_STRING_LENGTH( title), TITLELENGTH);
    my_f2c_strncpy( button->command, command, CFC_STRING_LENGTH( command), COMMANDLENGTH);
    my_f2c_strncpy( button->label, label, CFC_STRING_LENGTH( label), LABELLENGTH);
    /*   button->showlength= strlen( button->label); */
    /* above is invalid because a blank character chain may be used
       even for a submenu button */
    button->showlength = *length;
    my_f2c_strncpy( button->helptxt, helptxt, CFC_STRING_LENGTH( helptxt), HLPFILELNGTH);
    my_f2c_strncpy( button->moretxt, moretxt, CFC_STRING_LENGTH( moretxt), TITLELENGTH);
    if (s_begin_group) {
        button->group = 1;
        s_begin_group = 0;
    } else {
        button->group = 0;
    }
}

static sic_task_t dialog_task = SIC_TASK_NULL;

static void kill_xwindow( )
{
    gag_trace( "<trace> kill_xwindow");
    if (sic_get_task_id(dialog_task) != sic_get_task_id(_sic_task_null)) {
        call_close_dialog_handler( );
        dialog_task = _sic_task_null;
    }
}

void CFC_API end_dialog( void)
{
    int i;

    kill_xwindow( );

    curr_widget = widgets;
    for (i = 0; i < NSTRUCT; i++) {
        widgets[i].generic.type = 0;
        strcpy( widgets[i].generic.variable, "\0");
    }
#ifdef WIN32
    call_on_end_dialog_handler( );
#endif
}

void CFC_API xgag_open( CFC_FString txt, CFC_FString name, int *code
                        CFC_DECLARE_STRING_LENGTH(txt)
                        CFC_DECLARE_STRING_LENGTH(name) )
{
    end_dialog( );

    my_f2c_strncpy( window_title, txt, CFC_STRING_LENGTH( txt), TITLELENGTH);
    my_f2c_strncpy( helpfilename, name, CFC_STRING_LENGTH( name), HLPFILELNGTH);
#ifdef WIN32
    call_on_open_dialog_handler( helpfilename);
#endif
}

void CFC_API xgag_update( CFC_FString variable, CFC_FzString update
                          CFC_DECLARE_STRING_LENGTH(variable) )
{
    int i;
    char variable_name[SICVLN];
    char *value = CFC_fz2c_string( update);

    if (sic_get_task_id(dialog_task) == sic_get_task_id(_sic_task_null))
        return;

    my_f2c_strncpy( variable_name, variable, CFC_STRING_LENGTH( variable), SICVLN);

    /* check if update is necessary */
    /* every instance of widget pointing on a variable will be updated by
       the widget sic_dialog (care has been taken for that in the widget 
       itself). It is only necessary to signal the FIRST widget (containing
       the associated variable name) to sic_dialog */
    for (i = 0; i < NSTRUCT; i++) {
        global_struct *widget = widgets + i;

        if (widget->generic.type == 0)
            break;

        if (strcmp( variable_name, widget->generic.variable) == 0) {
            int fire = 0;
            double double_value;
            int int_value;

            switch (widget->generic.type) {
            case SLIDER:
                double_value = atof( value);
                if (widget->slider.uservalue != double_value) {
                    widget->slider.uservalue = double_value;
                    fire = 1;
                }
                break;
            case LOGIC:
                int_value = strcmp( value, "YES") ? 0 : 1;
                if (widget->logic.userlogic != int_value) {
                    widget->logic.userlogic = int_value;
                    fire = 1;
                }
                break;
            case CHAIN:
                if (strncmp( widget->chain.userchain, value, CHAINLENGTH)) {
                    strncpy( widget->chain.userchain, value, CHAINLENGTH);
                    fire = 1;
                }
                break;
            case CHOICE:
                if (strncmp( widget->choice.userchoice, value, CHAINLENGTH)) {
                    strncpy( widget->choice.userchoice, value, CHAINLENGTH);
                    fire = 1;
                }
                break;
            case BROWSER:
                if (strncmp( widget->file.userchain, value, CHAINLENGTH)) {
                    strncpy( widget->file.userchain, value, CHAINLENGTH);
                    fire = 1;
                }
                break;
            }

            if (fire) {
                sic_fire_modified_variable_event( i, widget);
            }

            return;                    /* exit from loop at first instance */
        }
    }
}

/* Get parameter function called by main task when it receives GUI\END */

void CFC_API get_user_input( int *err_code)
{
    int i;
    int nb_widgets;

    nb_widgets = sic_open_widget_board( );

    for (i = 0; i < nb_widgets; i++) {
        sic_get_widget_def( i, &widgets[i]);
    }

    *err_code = sic_get_widget_returned_code( );

    sic_close_widget_board( );

    if (*err_code != -3) {
        for (i = 0; i < NSTRUCT; i++) {
            if (widgets[i].generic.type == 0)
                break;

            switch (widgets[i].generic.type) {
            case CHAIN:
                my_c2fp_strcpy( widgets[i].chain.chain, widgets[i].chain.userchain, CHAINLENGTH);
                break;
            case BROWSER:
                my_c2fp_strcpy( widgets[i].file.chain, widgets[i].file.userchain, CHAINLENGTH);
                break;
            case LOGIC:
                *widgets[i].logic.logic = widgets[i].logic.userlogic;
                break;
            case SLIDER:
                *widgets[i].slider.value = widgets[i].slider.uservalue;
                break;
            case CHOICE:
                my_c2fp_strcpy( widgets[i].choice.choice, widgets[i].choice.userchoice, CHAINLENGTH);
                break;
            }
        }
    } else {
        /* Reset structure members to initial values */
        for (i = 0; i < NSTRUCT; i++) {
            if (widgets[i].generic.type == 0)
                break;
            switch (widgets[i].generic.type) {
            case LOGIC:
                widgets[i].logic.userlogic = *widgets[i].logic.logic;
                break;

            case BROWSER:
                my_fp2c_strncpy( widgets[i].file.userchain, widgets[i].file.chain, widgets[i].file.length, CHAINLENGTH);
                break;
            case CHOICE:
                my_fp2c_strncpy( widgets[i].choice.userchoice, widgets[i].choice.choice, widgets[i].choice.length, CHAINLENGTH);
                break;
            }
        }
    }
}

void CFC_API xgag_dialog( CFC_FzString return_command, int *err_code)
{
    int i;

    sic_create_widget_board( );

    sic_set_widget_global_infos( window_title, helpfilename, CFC_fz2c_string( return_command));

    /* write data in shared memory */
    for (i = 0; widgets[i].generic.type != 0; i++) {
        sic_add_widget_def( &widgets[i]);
    }

    sic_close_widget_board( );

    if (dialog_task = launch_dialog( )) {
        sic_wait_widget_created( );
    }
}

void CFC_API xgag_begin_group( CFC_FzString label)
{
    s_begin_group = 1;
}

void CFC_API xgag_end_group( )
{
    global_struct *tmp = curr_widget;
    while (tmp > widgets) {
        tmp--;
        if (tmp->generic.type == BUTTON) {
            tmp->button.group = -1;
            break;
        }
    }
}

/* Pause the execution of an temporary X-Window detached process
   end inhibate command in permanent X-Menus which beep on user actions */
void CFC_API xgag_wait( )
{
    /* Pause, allowing a dialog panel to set a number of variables
     * while executing a procedure. Unsupported with client server XML.
     */
    static int notDone = 1, doWait = 1;

    if (notDone) {
        if (strcasecmp( sic_s_get_translation( "GAG_WIDGETS"), "XML") == 0) {
            doWait = 0;
            notDone = 0;
        }
    }
    if (doWait) {
        command_line_t command_line;
        sic_command_from_t from;

        printf( "Waiting ...\n");

        /* TODO: look for the good place to call sic_wait_command */
        /* wait for GUI\END */
        do {
            sic_wait_command( &command_line, &from);
        } while (strcmp( command_line.line, "GUI\\END") && from != SIC_CHILD_EXIT);
    } else {
        ggui_c_message(seve_w, "XGAG_WAIT", "GUI\\PAUSE unsupported by XML widgets,");
        printf( "will give unpredictable results.\n");
    }
}

