/*
 * gnoclRadioButton.c --
 *
 * This file implements the radioButton widget
 *
 * Copyright (c) 2001 - 2002 P.G. Baum
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

/*
   History:
        01: new command "getValue", "setValue"
   2002-01: new command "invoke"
        09: underlined accelerators 
   2001-04: Begin of developement
 */

#include "gnocl.h"
#include <string.h>
#include <assert.h>

/*
#define DEBUG_PRINT
*/

typedef enum {  EMPTY = 0, 
                DONT_COMMAND = 1 << 0, 
                DONT_SET     = 1 << 1, 
                DONT_GET     = 1 << 2,
                ALL          = 0xFF } InSignalType;

typedef struct {
   GArray       *buttons;
   InSignalType inSignal;
   char         *variable;
   /* GSList    *gtkGroup; */
} Gnocl_RadioGroup;
static GData *radioGroup = NULL;

typedef struct
{
   GNOCL_STD_OPTIONS_VARS;

   Gnocl_ObjBool    text;
   Gnocl_StringBool command;
   Gnocl_StringBool variable;
   Gnocl_ObjBool    value;
   Gnocl_BoolBool   indicatorOn;
} RadioButtonOptions;

typedef struct
{
   char             *name;
   Tcl_Interp       *interp;
   Gnocl_RadioGroup *group;
   GtkRadioButton   *radioButton;
   char             *command;
   Tcl_Obj          *value;
} RadioButtonParams;

static const GnoclWidgetOptions widgetOptions[] = 
{
   GNOCL_STD_OPTIONS_NAMES( RadioButtonOptions ),

   { "-text", GNOCL_OBJ, GNOCL_OFFSET( RadioButtonOptions, text ) },
   { "-command", GNOCL_STRING, GNOCL_OFFSET( RadioButtonOptions, command ) },
   { "-variable", GNOCL_STRING, GNOCL_OFFSET( RadioButtonOptions, variable ) },
   { "-value", GNOCL_OBJ, GNOCL_OFFSET( RadioButtonOptions, value ) },
   { "-indicatorOn", GNOCL_BOOL, GNOCL_OFFSET( RadioButtonOptions, 
         indicatorOn ) },
   { NULL, 0, 0 }
};

static void freeOpts( RadioButtonOptions *p )
{
   GNOCL_STD_OPTIONS_FREE( p );
   GNOCL_FREE_TCL_OPT( p->text );
   GNOCL_FREE_MEM_OPT( p->command );
   GNOCL_FREE_MEM_OPT( p->variable );
   GNOCL_FREE_TCL_OPT( p->value );
}

static RadioButtonParams *findSelectedButton( const Gnocl_RadioGroup *group )
{
   typedef RadioButtonParams *PRadioButtonParams;
   int k;
   assert( radioGroup != NULL );

   for( k = 0; k < group->buttons->len; ++k )
   {
      PRadioButtonParams p = g_array_index( group->buttons, 
            PRadioButtonParams, k );
      if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( p->radioButton ) ) )
      {
         #ifdef DEBUG_PRINT
         printf( "in findSelectedButton no: %d\n", k );
         #endif
         return p;
      }
   }

   return NULL;
}
static RadioButtonParams *selectRadioWithValue( Tcl_Obj *value, 
      const Gnocl_RadioGroup *group )
{
   typedef RadioButtonParams *PRadioButtonParams;
   int k;
   char *str = Tcl_GetString( value );
   assert( radioGroup != NULL );

   /* TODO: is it possible to deselect all radios? eg. if value is
            doesn't fit to any of the buttons? */
   for( k = 0; k < group->buttons->len; ++k )
   {
      PRadioButtonParams p = g_array_index( group->buttons, 
            PRadioButtonParams, k );
      if( strcmp( str, Tcl_GetString( p->value ) ) == 0 )
      {
         #ifdef DEBUG_PRINT
         printf( "in selectRadioWithValue no: %d val: %s\n", k, str );
         #endif
         gtk_toggle_button_set_active( 
               GTK_TOGGLE_BUTTON( p->radioButton ), 1 );
         return p;
      }
   }

   return NULL;
}

static int doCommand( RadioButtonParams *para, Tcl_Obj *val )
{
   if( para->command )
   {
      static Gnocl_PercSubst ps[] = {
         { 'w', GNOCL_STRING },  /* widget */
         { 'v', GNOCL_OBJ },     /* value */
         { 0 }
      };

      ps[0].val.str = para->name;
      ps[1].val.obj = val;
      #ifdef DEBUG_PRINT
      printf( "in command: \"%s\"\n", Tcl_GetString( val ) );
      #endif
      return gnoclPercentSubstAndEval( para->interp, ps, para->command );

   }
   return TCL_OK;
}

static int toggledFuncRet( RadioButtonParams *para, int force )
{
   int isSelected = gtk_toggle_button_get_active( 
         GTK_TOGGLE_BUTTON( para->radioButton ) ); 
   int ret = TCL_OK;

   /* call command only for selected button like tk */
   #ifdef DEBUG_PRINT
   printf( "vor toggledFuncRet isSelected: %d\n", isSelected ); 
   #endif
   if( force || isSelected )
   {
      Gnocl_RadioGroup *group = para->group;
      /* value is always the value of the selected button. On "invoke"
         or "setValue" this is *not* the current value */
      Tcl_Obj *val = para->value;
      if( force )
      {
         RadioButtonParams *p = findSelectedButton( group );
         if( p )
            val = p->value;
      }

      if( (group->inSignal & DONT_SET ) == 0 )
      {
         #ifdef DEBUG_PRINT
         printf( "in setVar: \"%s\"\n", Tcl_GetString( val ) ); 
         #endif
         group->inSignal |= DONT_GET;
         if( Tcl_SetVar2Ex( para->interp, group->variable, NULL, 
               val, TCL_GLOBAL_ONLY ) == NULL )
            ret = TCL_ERROR;
         group->inSignal &= ~DONT_GET;
      }
      if( (group->inSignal & DONT_COMMAND ) == 0 )
         if( doCommand( para, val ) != TCL_OK )
            ret = TCL_ERROR;
   }

   return ret;
}

static void radioButtonToggledFunc( GtkWidget *widget, gpointer data )
{
   toggledFuncRet( (RadioButtonParams *)data, 0 );
}

static char *radioButtonTraceFunc( ClientData data,
      Tcl_Interp *interp, char *name1, char *name2, int flags)
{
   Gnocl_RadioGroup *para = (Gnocl_RadioGroup *)data;

   #ifdef DEBUG_PRINT
   printf( "vor radioButtonTraceFunc %d\n", para->inSignal );
   #endif
   if( para->inSignal == 0 )
   {
      Tcl_Obj *obj = Tcl_GetVar2Ex( interp, name1, name2, flags );
      if( obj != NULL )
      {
         RadioButtonParams *rp;
         #ifdef DEBUG_PRINT
         printf( "in radioButtonTraceFunc: \"%s\"\n", Tcl_GetString( obj ) );
         #endif
         para->inSignal |= DONT_SET|DONT_COMMAND;
         /* toggledFuncRet is only automatically called if the status of
            the check button is changed, i.e. it was not 'on' */
         rp = selectRadioWithValue( obj, para );
         para->inSignal &= ~(DONT_SET|DONT_COMMAND);
         if( rp != NULL )
         {
            doCommand( rp, rp->value );
         }
         else
            printf( "DEBUG: what to do? no corespondig radio button "
                  "to val \"%s\"\n", Tcl_GetString( obj ) );
      }
   } 
   /* radioButtonCmdFunc( para->radioButton, data ); */

   return NULL;
}


static Gnocl_RadioGroup *findGroupFromVariable( const char *var )
{
   if( var == NULL )
      return NULL;

   return g_datalist_get_data( &radioGroup, var );
}

/*
static GSList *getGtkGroupFromGroup( Gnocl_RadioGroup *group )
{
   
   if( group == NULL )
      return NULL;

   return group->gtkGroup;
}
*/

static Gnocl_RadioGroup *addRadioToGroup( Tcl_Interp *interp, 
      char *var, RadioButtonParams *radio )
{
   typedef RadioButtonParams *PRadioButtonParams;
   Gnocl_RadioGroup *group = findGroupFromVariable( var );
   Tcl_Obj *varObj;

   if( group == NULL )
   {
      /* new group */
      group = g_new( Gnocl_RadioGroup, 1 );
      /* group->gtkGroup = gtk_radio_button_group( radio->radioButton ); */
      /* g_data_init( &group->buttons ) */
      group->buttons = g_array_new( 0, 0, sizeof( PRadioButtonParams ) );
      group->variable = g_memdup( var, strlen( var ) + 1 );
      group->inSignal = 0;

      g_datalist_set_data( &radioGroup, group->variable, group );


      varObj = Tcl_GetVar2Ex( interp, group->variable, NULL, 
            TCL_GLOBAL_ONLY );
      if( varObj == NULL ) 
      {
         #ifdef DEBUG_PRINT
         printf( "variable \"%s\" does not exist -> setting to \"%s\"\n", 
               group->variable, Tcl_GetString( radio->value ) );
         #endif
         /* variable does not exist */
         Tcl_SetVar2Ex( interp, group->variable, NULL, 
               radio->value, TCL_GLOBAL_ONLY );
      }

      Tcl_TraceVar( interp, var, TCL_TRACE_WRITES | TCL_GLOBAL_ONLY, 
            radioButtonTraceFunc, group );
      /* FIXME: Test return of Tcl_TraceVar */
   }
   else
   {
      PRadioButtonParams p = g_array_index( group->buttons, 
            PRadioButtonParams, 0 );
      GSList *gtkGroup = gtk_radio_button_group( p->radioButton );
      /* printf( "radioButton: %p gtkGroup: %p\n", p->radioButton, gtkGroup ); 
      */
      gtk_radio_button_set_group( radio->radioButton, gtkGroup );

      assert( group->buttons->len > 0 );

      varObj = Tcl_GetVar2Ex( interp, group->variable, NULL, 
            TCL_GLOBAL_ONLY );
      if( strcmp( Tcl_GetString( varObj ), 
            Tcl_GetString( radio->value ) ) == 0 )
      {
         #ifdef DEBUG_PRINT
         printf( "setting active\n" );
         #endif
         gtk_toggle_button_set_active( 
               GTK_TOGGLE_BUTTON( radio->radioButton ), 1 );
      }
   }
   group->buttons = g_array_append_val( group->buttons, radio );
   return group;
}

static int deleteRadioFromGroup( Gnocl_RadioGroup *group, 
      RadioButtonParams *radio )
{
   int k;
   typedef RadioButtonParams *PRadioButtonParams;

   assert( group != NULL );

   for( k = 0; k < group->buttons->len; ++k )
   {
      PRadioButtonParams p = g_array_index( group->buttons, 
            PRadioButtonParams, k );
      if( p->radioButton == radio->radioButton )
      {
         g_array_remove_index_fast( group->buttons, k );
         break;
      }
   }
   assert( k <= group->buttons->len );

   return group->buttons->len;
}

static void radioButtonDestroyFunc( GtkWidget *widget, gpointer data )
{
   RadioButtonParams *para = (RadioButtonParams *)data;

   gnoclForgetWidgetFromName( para->name );
   Tcl_DeleteCommand( para->interp, para->name );

   if( deleteRadioFromGroup( para->group, para ) == 0 )
   {
      Gnocl_RadioGroup *group = para->group;
      Tcl_UntraceVar( para->interp, group->variable, 
            TCL_TRACE_WRITES | TCL_GLOBAL_ONLY, radioButtonTraceFunc, group );

      /* remove complete group */
      g_array_free( group->buttons, 1 );
      g_datalist_remove_data( &radioGroup, group->variable );
      g_free( group );
   }

   if( para->value ) 
      Tcl_DecrRefCount( para->value );

   g_free( para->command );

   g_free( para->name );
   g_free( para );
}

static int radioButtonConfigure( Tcl_Interp *interp, RadioButtonParams *para,
   RadioButtonOptions *popt )
{
   GNOCL_STD_OPTIONS_HANDLE( popt, GTK_WIDGET( para->radioButton ) );

   if( popt->text.changed )
   {
      GnoclStringType type = gnoclGetStringType( popt->text.val );
      char *txt = gnoclGetString( popt->text.val );
      GtkLabel *label = gnoclFindLabel( GTK_WIDGET( para->radioButton ) );
      if( label )
      {
         gtk_label_set_text( label, txt );
         if( type & GNOCL_STR_UNDERLINE )
         {
            guint key = gnoclLabelParseULine( GTK_WIDGET( label ), txt );
            if( key != GDK_VoidSymbol )
            {
               gtk_widget_add_accelerator( GTK_WIDGET( para->radioButton ), 
                     "clicked", gnoclGetAccelGroup(), 
                     key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED );
            }
         }
      }
   }

   if( popt->indicatorOn.changed )
      gtk_toggle_button_set_mode( GTK_TOGGLE_BUTTON( para->radioButton ), 
            popt->indicatorOn.val );

   if( popt->value.changed )
   {
      if( para->value ) Tcl_DecrRefCount( para->value );
      para->value = popt->value.val;
      popt->value.val = NULL;
   }

   if( popt->command.changed )
   {
      g_free( para->command );
      para->command = popt->command.val;
      popt->command.val = NULL;
   }

   return TCL_OK;
}

static int radioButtonFunc( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = { GNOCL_STD_CMD_NAMES,
         "configure", "getValue", "setValue", "invoke", NULL };
   enum cmdIdx { GNOCL_STD_CMD_ENUMS, 
         ConfigureIdx, GetValueIdx, SetValueIdx, InvokeIdx };
   RadioButtonParams *para = (RadioButtonParams *)data;
   GtkWidget *widget = GTK_WIDGET( para->radioButton );
   int idx;

   if( Tcl_GetIndexFromObj( interp, objv[1], cmds, "command", 
         TCL_EXACT, &idx ) != TCL_OK )
      return TCL_ERROR;

   switch( idx )
   {
      GNCOL_STD_CMD_CASE( interp, objc, objv, widget );
            break;
      case ConfigureIdx:
            {
               RadioButtonOptions opt;
               int ret = TCL_ERROR;
               memset( &opt, 0, sizeof( opt ) );
               if( gnoclParseAllWidgetOpts( interp, objc, objv, 1,
                     widgetOptions, (char *)&opt ) == TCL_OK )
                  ret = radioButtonConfigure( interp, para, &opt );

               freeOpts( &opt );
               if( ret != TCL_OK )
                  return TCL_ERROR;
            }
            break;
      case GetValueIdx:
            {
               Gnocl_RadioGroup *group = para->group;
               int k;
               typedef RadioButtonParams *PRadioButtonParams;

               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, NULL );
                  return TCL_ERROR;
               }
               for( k = 0; k < group->buttons->len; ++k )
               {
                  PRadioButtonParams p = g_array_index( group->buttons, 
                        PRadioButtonParams, k );
                  if( gtk_toggle_button_get_active( 
                        GTK_TOGGLE_BUTTON( p->radioButton ) ) )
                  {
                     Tcl_SetObjResult( interp, p->value );
                     return TCL_OK;
                  }
               }
            }
            break;
      case SetValueIdx:
            {
               RadioButtonParams *rp;
               if( objc != 3 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "value" );
                  return TCL_ERROR;
               }

               para->group->inSignal |= DONT_SET|DONT_COMMAND;
               /* toggledFuncRet is only automatically called if the status of
                  the check button is changed, i.e. it was not 'on' */
               rp = selectRadioWithValue( objv[2], para->group );
               para->group->inSignal &= ~(DONT_SET|DONT_COMMAND);
               if( rp == NULL )
               {
                  Tcl_AppendResult( interp, "No radio button with value \"",
                        Tcl_GetString( objv[2] ), "\" defined." );
                  return TCL_ERROR;
               }
               return toggledFuncRet( para, 1 ); 
            }
            break;
      case InvokeIdx:
            if( GTK_WIDGET_IS_SENSITIVE( GTK_WIDGET( para->radioButton ) ) )
               gtk_button_clicked( GTK_BUTTON( para->radioButton ) ); 
            break;
            /*
            {
               para->group->inSignal |= (DONT_SET | DONT_COMMAND);
               gtk_toggle_button_set_active( 
                     GTK_TOGGLE_BUTTON( para->radioButton ), 1 );
               para->group->inSignal &= ~(DONT_SET | DONT_COMMAND);
               return toggledFuncRet( para, 0 ); 

            }
            */
   }

   return TCL_OK;
}

int gnoclRadioButtonCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static int radioGroupNotInitialized = 1;
   RadioButtonOptions opt;
   RadioButtonParams  *para;
   /* Gnocl_RadioGroup   *group; */


   if( radioGroupNotInitialized )
   {
      radioGroupNotInitialized = 0;
      g_datalist_init( &radioGroup );
   }
   
   memset( &opt, 0, sizeof( opt ) );
   if( gnoclParseAllWidgetOpts( interp, objc, objv, 0,
         widgetOptions, (char *)&opt ) != TCL_OK )
      return TCL_ERROR;

   if( opt.value.changed == 0 || opt.variable.changed == 0 )
   {
      Tcl_SetResult( interp, 
            "Option \"-value\" and \"-variable\" are required.", TCL_STATIC );
      return TCL_ERROR;
   }

   para = g_new( RadioButtonParams, 1 );
   para->interp = interp;
   para->name = gnoclGetAutoWidgetId();
   para->command = NULL;
   para->value = NULL;

   para->radioButton = GTK_RADIO_BUTTON( 
         gtk_radio_button_new_with_label( NULL, "" ) );
         /* NULL, gnoclGetString( opt.text.val ) ) ); */
         /*
         getGtkGroupFromGroup( group ), para->text ) );
         */
   
   if( radioButtonConfigure( interp, para, &opt ) != TCL_OK )
   {
      /* FIXME: g_free para->name ... */
      gtk_widget_destroy( GTK_WIDGET( para->radioButton ) );
      return TCL_ERROR;
   }

   para->group = addRadioToGroup( interp, opt.variable.val, para );

   gtk_signal_connect( GTK_OBJECT( para->radioButton ), "toggled", 
         GTK_SIGNAL_FUNC( radioButtonToggledFunc ), para );

   gtk_signal_connect( GTK_OBJECT( para->radioButton ), "destroy", 
         GTK_SIGNAL_FUNC( radioButtonDestroyFunc ), para );
 
   /*
      gtk_signal_connect( GTK_OBJECT( para->radioButton ), "clicked", 
            GTK_SIGNAL_FUNC( radioButtonCmdFunc ), para );
   */

   gnoclMemNameAndWidget( para->name, GTK_WIDGET( para->radioButton ) );
   gtk_widget_show( GTK_WIDGET( para->radioButton ) );

   Tcl_CreateObjCommand( interp, para->name, radioButtonFunc, para, NULL );

   Tcl_SetObjResult( interp, Tcl_NewStringObj( para->name, -1 ) );

   return TCL_OK;
}

