/*
 * ===========================
 * langdrill: Language Drills
 * Version 0.1.7
 * 1/1999
 * ===========================
 *
 * Copyright (C) 1998, Ionutz Borcoman
 * Developed by Ionutz Borcoman <borco@usa.net>, <borco@borco-ei.eng.hokudai.ac.jp>
 *
 * Many thanks to Mario Motta <mmotta@guest.net> for the help given
 * during developing this program.
 *
 * 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 <vdk/FileTreeDialog.h>
#include "main.hh"
#include <locale.h>
#include <gdk/gdkkeysyms.h>

#define SOLUTIONS_PAGE		0
#define QUIZZES_PAGE		1
#define SIMPLEQUIZZ_PAGE	2

DEFINE_SIGNAL_MAP(MyLangForm, VDKForm)
  ON_SIGNAL( _book, switch_page_signal, SwitchPage ),
  ON_SIGNAL( _clist, select_row_signal, SelectRow ),
  ON_SIGNAL( _clist, unselect_row_signal, SelectRow ),
  ON_SIGNAL( _aboutMenu, activate_signal, AboutClicked ),
  ON_SIGNAL( _exitMenu, activate_signal, ExitClicked ),
  ON_SIGNAL( _loadButton, clicked_signal, LoadClicked ),
  ON_SIGNAL( _reloadButton, clicked_signal, ReloadClicked ),
  ON_SIGNAL( _senseGroup, toggled_signal, SenseToggled ),
  ON_SIGNAL( _startButton, clicked_signal, StartClicked ),
  ON_SIGNAL( _simpleQuizzButton, clicked_signal, QuizzAnswerClicked),
  ON_SIGNAL( _timer, timer_tick_signal, TimerTick )
  END_SIGNAL_MAP;

// Is this working ?
// DEFINE_EVENT_MAP(MyLangForm, VDKForm)
//   ON_EVENT( _simpleQuizzText, key_press_event, QuizzTextKeyPress )
// END_EVENT_MAP

DEFINE_SIGNAL_LIST(MyLangForm, VDKForm);
DEFINE_EVENT_LIST(MyLangForm, VDKForm);


bool
MyLangForm::changeAnswerButtons()
{
  // check to see if the input values are usable
  int newNr = atoi(_quizzAnswerNrEntry->Text);
  int wQuizzTmp = atoi(_setup.wQuizz);
  int hQuizzTmp = atoi(_setup.hQuizz);

  if(newNr<=2){
    _sbar->Push("The program needs at least 2 possible answers.");
    return false;
  }

  int oldNr = atoi(_setup.quizzAnswerNr);

  // delete the old buttons only if their number has changed
  QuizzDestroy( newNr, oldNr );

  QuizzSetup( newNr, oldNr, wQuizzTmp, hQuizzTmp );

  // now update the values of _setup
  sprintf( _setup.quizzAnswerNr, _quizzAnswerNrEntry->Text);

  return true;
}


void
MyLangForm::checkSections()
{
  if( _clist->Tuples.size()<=0){
    _startButton->Enabled = false;
  } else{
    _startButton->Enabled = true;
  }
}


void
MyLangForm::clearLessons()
{
  for(int i=0; i<_setup.lessonNr; i++){
    delete [] _setup.lessonTitle[i];
  }

  delete [] _setup.lessonTitle;

  if(_config)
    delete _config;
}


void
MyLangForm::distribute(int *array, int nr, int nr_avail)
{
  int *occupied;
  int i, j, k;

  occupied = new int[nr_avail];
  memset( occupied, 0, nr_avail*sizeof(int) );

  for(i=0; i<nr; i++){
    k=0;
    for(j=0; j<nr_avail; j++){
      if(occupied[j]!=0)
        continue;
      else
        if(array[i]==k){
          occupied[j]=1;
          array[i]=j;
          break;
        }
        else
          k++;
    }
  }

  delete [] occupied;
}


// searches for the RC file
bool
MyLangForm::findLangDrillRC()
{
  bool rcNotFound = true;

  // search for a config file given on the command line or
  // for a file configuration file
  switch(*_argc){
  case 2:
    // check the given rc file
    if (access(_argv[1], R_OK)==0){
      rcNotFound = false;
      sprintf(_setup.name,_argv[1]);
      printf("Drill file: %s\n", _setup.name);
      break;
    } else {
      // if file on the command line not finded
      printf("%s drill file not found. Search for default files now ...\n",
             _argv[1]);
    }
  case 1:
    // no options; search for default locations
    if (access(DRILL_DEF, R_OK)==0){
      // CONFIG\FILE is found in the current directory
      char dirName[PATH_MAX];
      rcNotFound = false;
      sprintf(_setup.name,"%s/%s", getcwd(dirName,PATH_MAX), DRILL_DEF);
      printf("Drill file: %s\n", _setup.name);
      break;
    } else if ( access(DRILL_DIR "/" DRILL_DEF, R_OK)==0){
      // file CONFIG_FILE file is found in the CONFIG_DIR directory
      rcNotFound = false;
      sprintf(_setup.name, DRILL_DIR "/" DRILL_DEF);
      printf("Drill file: %s\n", _setup.name);
      break;
    } else {
      // no config files found
      printf("No drill found.\n\n");
    }
  default:
    printf("Language Drills usage\n"
           "\n"
           "langdrill [drill_file]\n"
           "\n"
           "If no drill_file is given, then %s is searched in \n"
           "the current directory and, after that, in %s.\n"
           "If a drill_file is given, absolute name must be suplied.\n",
           DRILL_DEF, DRILL_DIR);
  }

  return rcNotFound;
}


void
MyLangForm::loadLangDrillRC(bool isNew, char *tempFile)
{
  // try to parse the new RC file
  MyLangSetup tempSetup;
  sprintf(tempSetup.name, tempFile);
  MyConfig *tempConfig = new MyConfig(tempFile);
  if( parseLangDrillRC(tempConfig, tempSetup)==false ){
    _sbar->Push("Could not load the file. Reverting to old values.");
    delete tempConfig;
    return;
  }

  // we save the current selections
  VDKArray<int> selected;
  VDKArray<VDKString> selectedNames;
  int selectedNr;

  if(isNew==false){
    selected = _clist->Selections();
    selectedNr = selected.size();
    selectedNames.resize(selectedNr);
    for(int i=0; i<selectedNr; i++){
      selectedNames[i]=(char*)_clist->Tuples[(selectedNr?selected[i]:i)][0];
    }
  }

  // now clear the _clist
  _clist->Clear();

  // save some old values
  int oldQuizzNr = atoi (_setup.quizzAnswerNr);

  // we clear the previous data
  clearLessons();

  // set the new values
  _config = tempConfig;
  _setup = tempSetup;

  // we overide what's in the RC file with what's in
  // the _quizzAnswerNrEntry
  int newQuizzNr = atoi (_quizzAnswerNrEntry->Text);
  int newWQuizz = atoi (_setup.wQuizz);
  int newHQuizz = atoi (_setup.hQuizz);

  // now add the rows to _clist
  // the _setup.lessonNr can be different now !
  for(int i=0; i<_setup.lessonNr; i++){
    _clist->AddRow( &(_setup.lessonTitle[i]) );
  }

  QuizzDestroy( newQuizzNr, oldQuizzNr );
  QuizzSetup( newQuizzNr, oldQuizzNr, newWQuizz, newHQuizz );

  char * tmpName;
  tmpName = new char[ strlen(_setup.directSenseName)
                    + strlen(_setup.reverseSenseName)
                    + 4 ];
  sprintf(tmpName, "%s > %s", _setup.directSenseName, _setup.reverseSenseName);
  _senseGroup->Buttons[0]->Caption =tmpName;
  sprintf(tmpName, "%s > %s", _setup.reverseSenseName, _setup.directSenseName);
  _senseGroup->Buttons[1]->Caption = tmpName;
  delete []tmpName;

  // overide the sense from the loaded file with
  // what the user has already chosen
  _setup.sense = _senseGroup->Selected;
  sprintf( _setup.quizzAnswerNr, _quizzAnswerNrEntry->Text );

  // if we have just reloaded,
  // the we checked  back the old selections
  // a check must be made to ensure that no problem arrises
  // if there are less lessons
  if(isNew==false){
    for(int j=0; j<selectedNr;j++){
      for(int i=0; i<_clist->Size(); i++){
        if(_clist->Tuples[i][0] == selectedNames[j]){
          _clist->SelectRow(i,0);
          break;
        }
      }
    }
  }

  _clist->Thaw();

  checkSections();

  // depending on what page we are viewing
  // we take corresponding action
  switch(_book->ActivePage){
  case QUIZZES_PAGE:
  case SIMPLEQUIZZ_PAGE:
    // force restart
    StartClicked(NULL);
    break;
  case SOLUTIONS_PAGE:
    SelectRow(NULL);
    break;
  }

  printf("Loaded drill file: %s\n", _setup.name);
}


void
MyLangForm::newRandowSet(int rows, int cols, int *set)
{
  int i, j;
  int tmp;
  int *tmpcol;
  int *tmprow;

  tmpcol = new int[cols];
  tmprow = new int[rows];

  for(i=0; i<cols; i++){
    tmp = (int)(((float)rand()*(cols-i)*rows/((float)(RAND_MAX))));
    tmpcol[i]=tmp%(cols-i);
    tmprow[i]=tmp/cols;
  }

  // find the right cols
  distribute(tmpcol, cols, cols);
  // find the right rows
  distribute(tmprow, cols, rows);

  for(i=0; i<rows; i++){
    for(j=0; j<cols; j++){
      for(int k=0; k< cols; k++){
        if(tmprow[k]==i && tmpcol[k]==j){
          set[j]=i;
        }
      }
    }
  }

  delete [] tmpcol;
  delete [] tmprow;
}

bool
MyLangForm::parseLangDrillRC(MyConfig *tempConfig, MyLangSetup &tempSetup )
{
  // we must protect this against exceptions
  try{
    tempConfig->Read();
  } catch ( MyException * exception){
    printf( exception->Message());
    printf("\n");
    delete exception;
    return false;
  }
  MySection	*sect;
  MyKey		*key;
  sect = tempConfig->Section("Application");
  if(sect){
    if((key = sect->Key("compatVersion")))
      strncpy(tempSetup.compatVersion, key->Get(), 9);
    if((key = sect->Key("sense")))
      tempSetup.sense=key->GetInt();
    if((key = sect->Key("directSenseName")))
      strncpy(tempSetup.directSenseName, key->Get(), MAX_SENSE_NAME-1);
    if((key = sect->Key("reverseSenseName")))
      strncpy(tempSetup.reverseSenseName, key->Get(), MAX_SENSE_NAME-1);
    if((key = sect->Key("quizzAnswerNr")))
      strncpy(tempSetup.quizzAnswerNr, key->Get(), 2);
    if((key = sect->Key("timer")))
      strncpy(tempSetup.timer, key->Get(), 2);
    if((key = sect->Key("limitedAnswerNr")))
      strncpy(tempSetup.limitedAnswerNr, key->Get(), 3);
    if((key = sect->Key("limitedAnswers")))
      tempSetup.limitedAnswers = key->GetInt();
    if((key = sect->Key("hideWindow")))
      tempSetup.hideWindow = key->GetInt();
    if((key = sect->Key("useTimer")))
      tempSetup.useTimer = key->GetInt();

    // search for lessons
    // one of the sections is the "Application" one
    tempSetup.lessonNr = tempConfig->Number() - 1;
    if(tempSetup.lessonNr ){
      tempSetup.lessonTitle = new char *[tempSetup.lessonNr];
      int i, j;
      for( i=0, j=0; i<tempConfig->Number(); i++){
        MySection	*newSect = tempConfig->operator[](i);
        if( sect != newSect ){
          tempSetup.lessonTitle[j] = strdup_new(newSect->Name());
          j++;
        }
      }
    }

  }else{
    // didn't find the main section
    // this section is vital
    // it contains at least one value not set by default
    // the version value
    return false;
  }
  return true;
}

void
MyLangForm::setActiveButtons(int keyTotalNr)
{
  int i;
  int cols = atoi(_setup.quizzAnswerNr);

  if( keyTotalNr <= cols ){
    // we have less quizzes than available buttons
    // we must disable some buttons
    for(i=0; i<keyTotalNr; i++){
      if( _quizzAnswerButton[i]->Enabled == false )
        _quizzAnswerButton[i]->Enabled = true;
    }
    for(i=keyTotalNr; i< cols; i++){
      // we disable suplimentar buttons
      // we also set their labels to "disabled"
      // to warn the user about their state
      if(_quizzAnswerButton[i]->Enabled == true )
        _quizzAnswerButton[i]->Enabled = false;
      _quizzAnswerButton[i]->Caption = "--disabled--";
    }
  }else{
    // we have more keys than available buttons
    // we verify that all buttons are active
    for(i=0; i<cols; i++){
      if( _quizzAnswerButton[i]->Enabled == false )
        _quizzAnswerButton[i]->Enabled = true;
    }
  }
  _simpleQuizzButton->Enabled = ( keyTotalNr > 0 );
  _simpleQuizzText->Enabled = ( keyTotalNr > 0 );
}

void
MyLangForm::showQuizz()
{
  int i;
  int j;
  int k;

  MySection *sect;
  // get the selected lessons
  VDKArray<int>& selected = _clist->Selections();
  int selectedNr = selected.size();

  // number of keys from selected lessons
  int keyTotalNr = 0;
  int *keyNr;
  keyNr = new int[(selectedNr?selectedNr:1)];

  // compute the total number of keys available
  // if nothing's selected, than use the first entry
  for(i=0; i<(selectedNr?selectedNr:1); i++){
    sect = _config->Section((char*)_clist->Tuples[(selectedNr?selected[i]:i)][0]);
    keyNr[i] = sect->Number();
    keyTotalNr += keyNr[i];
  }

  // we need to test if there are more buttons than available
  // keys. In such a case, we must take some precations
  setActiveButtons( keyTotalNr );

  // is this minimum operator portable ??
  // it should work with egcs at least !!
  int cols = keyTotalNr <? atoi(_setup.quizzAnswerNr);

  int rows = keyTotalNr;
  int *set = new int[cols];
  int *mset = new int[cols];
  newRandowSet(rows,cols,set);
  newRandowSet(cols,cols,mset);

  MyKey *key;

  // now choose the keys.
  for(i=0; i< cols; i++){
    j = 0;			// section index (we use the Tuples to get the section name)
    k = set[i];		// key index
    // get the section index and the key index in the corresponding section
    if(selectedNr){
      for(; k >= keyNr[j]; j++)
        k = k - keyNr[j];
    }
    sect = _config->Section((char*)_clist->Tuples[(selectedNr?selected[j]:j)][0]);
    key  = sect->operator[](k);
    _quizzAnswerButton[mset[i]]
      ->Caption = (_setup.sense==0)?key->Get():key->Name();
  }

  i=0;
  j = 0;				// section index (we use the Tuples to get the section name)
  k = set[i];			// key index
  // printf("quizz %d: key %d\n", _quizzes, k);
  // get the section index and the key index in the corresponding section
  if(selectedNr){
    for(; k >= keyNr[j]; j++)
      k = k - keyNr[j];
  }
  sect = _config->Section((char*)_clist->Tuples[(selectedNr?selected[j]:j)][0]);
  key  = sect->operator[](k);
  _quizzLabel->Caption = (_setup.sense==0)?key->Name():key->Get();
  _goodTag = mset[i];

  // set the simple quizz now
  i=0;
  j = 0;			// section index (we use the Tuples to get the section name)
  k = set[i];		// key index
  // get the section index and the key index in the corresponding section
  if(selectedNr){
    for(; k >= keyNr[j]; j++)
      k = k - keyNr[j];
  }
  sect = _config->Section((char*)_clist->Tuples[(selectedNr?selected[j]:j)][0]);
  _simpleQuizzKey  = sect->operator[](k);
  _simpleQuizzSense = _setup.sense;
  _simpleQuizzLabel->Caption = (_simpleQuizzSense==0)
    ?_simpleQuizzKey->Name()
    :_simpleQuizzKey->Get();

  delete [] mset;
  delete [] set;
  delete [] keyNr;
}


// return codes
// -1: the _timerEntry->Text is not valid
//  0: the _timerCheck is NOT checked
//  1: the _timerCheck IS checked
int
MyLangForm::useTimer()
{
  if( _timerCheck->Checked == true ){
    int temp;
    temp = atoi(_timerEntry->Text);
    // there is an error and we signal it
    if(temp<=0)
      return -1;
    _timerCicle = temp*60;
    _elapsedTime = _timerCicle;
    _timer->Start();
    _timerBar->Value = 0;
    _timerRunning = true;
  }else{
    _elapsedTime = 0;
    _timerRunning = false;
    // the user don't want timer
    return 0;
  }
  // the user want timer
  return 1;
}


MyLangForm::MyLangForm(VDKApplication *app, int *argc, char *argv[])
  :VDKForm(app,"Language Drills")
{
  // command line args
  _argc = argc;
  _argv = argv;

  // default setup values
  _setup.name[0]='\0';
  _setup.sense = 1;
  sprintf( _setup.quizzAnswerNr, "10");
  sprintf( _setup.wQuizz, "150");
  sprintf( _setup.hQuizz, "35");
  sprintf( _setup.directSenseName, "Japanese" );
  sprintf( _setup.reverseSenseName, "English" );
  sprintf( _setup.compatVersion, "00.00.00" );
  sprintf( _setup.timer, "10" ); // default to 10 minutes
  sprintf( _setup.limitedAnswerNr, "25" );

  _setup.lessonNr = 0;
  _setup.limitedAnswers = 1;	// we want limited answers
  _setup.useTimer = 1;			// we want to use the timer
  _setup.hideWindow = 0;		// by default we minimize the window

  _elapsedTime = 0;
  _timerCicle = 0;
  _timerRunning = false;

  _simpleQuizzKey = NULL;
  _simpleQuizzSense = _setup.sense;

  _config = NULL;

  _currentLesson = 0;
  _clistWordsUpdated = false;
};


MyLangForm::~MyLangForm()
{
  // the VDK objects are deleted by the garbage collector
  // we only need to delete the array of pointer we used internally
  // aka _quizzAnswerButton
  delete [] _quizzAnswerButton;
  delete [] _slot;

  clearLessons();
};


void
MyLangForm::hideOrIconify()
{
  printf("Stop the timer first if you want to exit. \n"
         "Now we hide app\n");
  // code to minimize/hide the app
  // I would preffer to minimize, but how do I do that ?!?
  if( _hideCheck->Checked == true )
    Hide();
  else{
    // check the state
    if( Iconized == false )
      Iconized = true;
  }
}


bool
MyLangForm::CanClose()
{
  // if user wants to use the timer, then do so
  if( _timerRunning == true ){
    printf("_timerRunning %d\n", _timerRunning);
    hideOrIconify();
  } else {
    switch( useTimer() ){
    case -1:
      return false;
    case 0:
      printf("Thanks for using Language Drills.\n");
      return true;
    case 1:
      hideOrIconify();
      break;
    default:
      return true; // we close the app by default
    }
  }
  return false;
}


void
MyLangForm::Setup()
{
  VDKRadioButton		*button;
  VDKBox				*vbox;
  VDKFrame				*senseFrame;
  VDKTable				*table;

  /////////////////////////////////
  // The menus
  /////////////////////////////////
  VDKMenubar * menubar;
  VDKMenuItem * menu;
  VDKMenu *menuContainer;
  menubar = new VDKMenubar(this);

  // the File menu
  menu = new VDKMenuItem(menubar, "File");
  menuContainer = new VDKMenu(this);
  _exitMenu = new VDKMenuItem(menuContainer, "Exit Langdrill");
  menu->Add( menuContainer );

  // the Help menu
  menu = new VDKMenuItem(menubar, "Help", NULL, r_justify);
  menuContainer = new VDKMenu(this);
  _aboutMenu = new VDKMenuItem(menuContainer, "About");
  menu->Add( menuContainer );

  Add(menubar, l_justify, false);

  /////////////////////////////////
  // Add a notebook and some pages to it
  /////////////////////////////////

  table = new VDKTable (this, 2, 2);
  _book = new VDKNotebook(this);

  // Add the notebook before adding any page to it.
  // Otherwise you can get sigsegvs at runtime !
  Add(table);
  table->Add(_book,1,2,0,1);

  // reinitialize the number generator
  srand(time(NULL));

  // search for a setup file
  // if one is finded, then the values from it are used
  // keys not found keeps the default values
  if(!findLangDrillRC()){
    _config = new MyConfig( _setup.name );
    parseLangDrillRC( _config, _setup );
  }

  /////////////////////////////////
  // Solutions Page
  // Attention ! This assumes that the _clist is already initialized
  char ** textWords;
  textWords = new char *[2];
  for(int i=0; i<2; i++)
    textWords[i] = new char[255];
  sprintf(textWords[0],_setup.directSenseName);
  sprintf(textWords[1],_setup.reverseSenseName);

  // using MULTIPLE we can select more than one line.
  // the lines selected can be obtained with Slections().
  _clistWords = new VDKCustomList( this, 2,
                                   textWords, GTK_SELECTION_MULTIPLE);
  _clistWords->ActiveTitles(false);

  // Strange, but adding here some Japanese text, makes it not to
  // be correctly displayed. Therefore we postponed this for the
  // time the user checks the first time the "Solutions" page

  //   MySection *sect = _config->Section((char*)_clist->Tuples[0][0]);
  //   for(int i=0; i<sect->Number(); i++){
  //	sprintf(textWords[0],sect->operator[](i)->Name());
  //	sprintf(textWords[1],sect->operator[](i)->Get());
  //	_clistWords->AddRow( textWords );
  //   }
  //   _clistWordsUpdated = true;

  for(int i=0; i<2; i++)
    delete [] textWords[i];
  delete [] textWords;

  _book->AddPage( _clistWords, "Words" );

  /////////////////////////////////
  // Quizzes Page
  // add the quizz entry and buttons
  // first initialize the _quizzBox
  _quizzFrame = new VDKFrame( this, "What's this ?",
                    v_box, shadow_etched_out );

  _quizzFrame->Add( _quizzLabel = new VDKLabel(this, " ") );

  _quizzBox = new VDKBox(this, v_box);
  _quizzBox->Add( _quizzFrame );

  int nr = atoi(_setup.quizzAnswerNr);
  int w = atoi(_setup.wQuizz);
  int h = atoi(_setup.hQuizz);
  QuizzSetup(nr, -1, w, h );
  // start with all butons disabled.
  // they wil be enabled when starting the quizzes
  for(int i = 0; i< atoi(_setup.quizzAnswerNr); i++)
    _quizzAnswerButton[i]->Enabled = false;

  // normal attachement
  _book->AddPage( _quizzBox, "Multiple Quizz" );

  /////////////////////////////////
  // Simple Quizz Page
  VDKBox *simpleBox = new VDKBox(this);
  VDKFrame *simpleFrame
    = new VDKFrame( this, "What's this ?",
                    v_box, shadow_etched_out );
  w = atoi(_setup.wQuizz);
  h= atoi(_setup.wQuizz);

  simpleFrame->Add( _simpleQuizzLabel = new VDKLabel(this, ""));
  simpleBox->Add( simpleFrame);
  simpleBox->Add( _simpleQuizzText = new VDKTextView(this));
  _simpleQuizzText->Editable = true;
  simpleBox->Add( _simpleQuizzButton
                  = new VDKCustomButton(this, "Submit",
                               "Press when you finish "
                               "entering the answer"));
  // start with the entry and the button disabled
  _simpleQuizzButton->Enabled = false;
  _simpleQuizzText->Enabled = false;

  // The last argument used to be "true". Now it must be "false" !
  EventConnect( _simpleQuizzText,
                "key_press_event",
                &MyLangForm::QuizzTextKeyPress, false);
  _book->AddPage( simpleBox, "Simple Quizz" );

  /////////////////////////////////
  // Timer Page
  VDKBox *timerBox = new VDKBox(this);
  timerBox->Add( new VDKLabel(this, "Time Step (minutes)"), l_justify, false);
  timerBox->Add( _timerEntry =
                 new VDKNumericEntry( this, 0, _setup.timer ),
                 l_justify, false);
  _timerEntry->Editable = true;
  timerBox->Add( new VDKLabel(this, "Answer Step"), l_justify, false);
  timerBox->Add( _answerStepEntry =
                 new VDKNumericEntry( this, 0, _setup.limitedAnswerNr ),
                 l_justify, false);
  _answerStepEntry->Editable = true;
  timerBox->Add( _hideCheck =
                 new VDKCheckButton( this, "Hide Window"),
                 l_justify, false);
  _hideCheck->Checked = _setup.hideWindow;
  timerBox->Add( _answerStepCheck =
                 new VDKCheckButton( this, "Use Answer Step" ),
                 l_justify, false);
  _answerStepCheck->Checked = _setup.limitedAnswers;
  timerBox->Add( _timerCheck =
                 new VDKCheckButton( this, "Use Timer"),
                 l_justify, false);
  _timerCheck->Checked = _setup.useTimer;

  timerBox->Add( _timerBar =
                 new VDKProgressBar( this ),
                 l_justify, false);
  _timerBar->Value = 0.0;
  _timerBar->PrelightBackground = clNavyBlue;
  _timerBar->NormalBackground = clIvory;
  // create a stopped timer wich emits signals at every second
  _timer = new VDKTimer(this, 1000, false);

  // we can't do this here. we do it after the window is shown
  // _timerBar->BarStyle = GTK_PROGRESS_DISCRETE;
  // _timerBar->BlockCount = 10;

  _book->AddPage( timerBox, "Timer Setup" );

  /////////////////////////////////////////////
  // The rest of the main page
  /////////////////////////////////////////////
  // sense frame
  vbox = new VDKBox(this, v_box);

  senseFrame = new VDKFrame(this, "Sense", v_box, shadow_etched_out );
  _senseGroup = new VDKRadioButtonGroup(this);

  char * tmpName;
  tmpName = new char[ strlen(_setup.directSenseName)
                    + strlen(_setup.reverseSenseName)
                    + 4 ];
  sprintf(tmpName, "%s > %s", _setup.directSenseName, _setup.reverseSenseName);
  button = new VDKRadioButton( _senseGroup, tmpName);
  sprintf(tmpName, "%s > %s", _setup.reverseSenseName, _setup.directSenseName);
  button = new VDKRadioButton( _senseGroup, tmpName);
  delete []tmpName;

  _senseGroup->SetSelected(_setup.sense);
  senseFrame->Add(_senseGroup);

  vbox->Add( senseFrame, l_justify, false );

  VDKFrame * answersFrame = new VDKFrame( this, "Possible Answers ( < 99 )",
                                          v_box, shadow_etched_out );
  answersFrame->Add( _quizzAnswerNrEntry =
                     new VDKNumericEntry(this, 2, _setup.quizzAnswerNr ),
                     l_justify, false);
  _quizzAnswerNrEntry->Editable = true;
  vbox->Add( answersFrame, l_justify, false);

  vbox->Add( _reloadButton =
             new VDKCustomButton( this, "Reload",
                             "Reload the current configuration file."),
             l_justify, false );
  vbox->Add( _loadButton =
             new VDKCustomButton( this, "Load ...",
                             "Load other configuration file"),
             l_justify, false );

  char *text = new char[255];;
  sprintf(text,"Lessons");

  // using MULTIPLE we can select more than one line.
  // the lines selected can be obtained with Selections().
  _clist = new VDKCustomList( this, 1, &text, GTK_SELECTION_MULTIPLE);
  _clist->ActiveTitles(false);
  for(int i=0; i<_setup.lessonNr; i++){
    _clist->AddRow( &(_setup.lessonTitle[i]) );
  }

  delete [] text;
  vbox->Add( _clist );
  vbox->Add( _startButton =
             new VDKCustomButton(this, "Start/Reset",
                            "Start/Restart quizzes ..."),
             l_justify, false );

  // we don't wont this to expand
  table->AddExt(vbox, 0, 1, 0, 1, GTK_FILL );

  table->AddExt(_sbar=new VDKStatusbar(this), 1, 2, 1, 2,
                GTK_FILL,
                GTK_FILL);

  _pbar = new VDKProgressBar(this);
  _pbar->Value = 1.0;
  _pbar->PrelightBackground = clNavyBlue;
  _pbar->NormalBackground = clIvory;
  // _pbar->BlockCount = 10;

  table->AddExt( _pbar, 0, 1, 1, 2, GTK_FILL, GTK_FILL );

  // check to see if we have at least one valid Section
  // and enables the start button accordingly.
  checkSections();
}


void
MyLangForm::QuizzDestroy( int newNr, int oldNr )
{
  if(newNr!=oldNr){
    for(int i=(oldNr-1); i>=0; i--){
      // disconect the buttons
      SignalDisconnect(_slot[i]);
      // never use delete for VDK objects
      _quizzAnswerButton[i]->Destroy();
      /////////////////////////
      // by mario
      /////////////////////////
      // alternatively you can use VDKObjectContainer::RemoveObject(obj)
      // _quizzBox->RemoveObject(_quizzAnswerButton[i]);
      // or VDKObjectContainer::RemoveObjects()
      // _quizzBox->RemoveObjects();
      // not your case since there are other obj's than buttons
      // into setupFrame.
      /////////////////////////
    }

    // but here we can use delete
    // this is a pointer to VDK objects
    delete [] _quizzAnswerButton;
    delete [] _slot;
  }
}


void
MyLangForm::QuizzSetup(int newNr, int oldNr, int w, int h)
{
  if(newNr!=oldNr){
    // add dynamically the buttons only if necessary
    _quizzAnswerButton = new VDKCustomButton *[newNr];
    _slot = new int[newNr];
    for(int i=0; i<newNr; i++){
      _quizzAnswerButton[i] = new VDKCustomButton(this, " ", "Click to choose this");
      _quizzAnswerButton[i]->SetSize(w,h);
      _quizzAnswerButton[i]->Tag = i;
      _quizzBox->Add( _quizzAnswerButton[i]);
      _slot[i] = SignalConnect(_quizzAnswerButton[i],
                               "clicked",
                               &MyLangForm::QuizzAnswerClicked);
    }
  }
  else{
    for(int i=0; i<newNr; i++){
      _quizzAnswerButton[i]->SetSize(w,h);
    }
  }
  _quizzLabel->SetSize(w,h);
}


bool
MyLangForm::AboutClicked( VDKObject*)
{
  Application()->
    MessageBox( " Let's talk about Language Drills",
                " Brought to you by\n"
                "   Ionutz Borcoman\n"
                "   <borco@usa.net>\n"
                "   <borco@borco-ei.eng.hokudai.ac.jp>\n"
                " \n"
                " Nothing would have been possible without\n"
                "   Mario Motta's VDK\n"
                "   <mmotta@guest.net>"
                "\n",
                MB_OK | MB_ICONINFORMATION ) ;
  return true;
}


bool
MyLangForm::ExitClicked( VDKObject*)
{
  // last chance if _timerCheck is set or
  // if _timer is running
  if( _timerRunning==true
      || _timerCheck->Checked==true ){
    bool answ = ( Application()->
                  MessageBox("Language Drill Message",
                             " Are you sure you want to close \n"
                             " the Language Drills ?\n"
                             " The Timer seems to be active !",
                             MB_YESNO)
                  == IDYES );
    if( answ == false ){
      return true;
    } else {
      _timerRunning = false;
      _timerCheck->Checked = false;
    }
  }
  Close();
  return true;
}


bool
MyLangForm::LoadClicked( VDKObject*)
{
  // open a FileSel (classical gtk+)
  char selection[FILENAME_MAX];
  sprintf(selection, "%s/*.drill", DRILL_DIR);
  VDKFileSel *child = new VDKFileSel(Application()->MainForm,
                                     selection,
                                     "Choose a lesson please");
  child->ShowModal();
  if( selection ){
    if(access(selection, R_OK)==0){
      loadLangDrillRC( true, selection );
      // we also change the working directory
      char *pos = strrchr(selection,'/');
      if( selection + FILENAME_MAX > pos )
        *pos = '\0';
    }
  }

//   FileStringArray selections;
//   VDKFileTreeDialog  *child = new VDKFileTreeDialog(Application()->MainForm,
//											&selections,
//											"Choose a lesson please");
//   child->ShowModal();
//   if(selections.size()>0){
//	if(access(selections[0], R_OK)==0){
//	  sprintf( _setup.name, selections[0]);
//	  ReloadClicked(NULL);
//	  printf("Loaded %s.\n", _setup.name);
//	}
//   }

  return true;
}

bool
MyLangForm::QuizzTextKeyPress( VDKObject *obj, GdkEvent *ev)
{
  // printf("debug\n");
  if( obj != _simpleQuizzText )
    return false;
  GdkEventKey *event = (GdkEventKey*)ev;
  if( event->keyval == GDK_Return ){
    // by this we stop propagation of RETURN
    // very cool. we have now a text wich acts like an entry
    gtk_signal_emit_stop_by_name( GTK_OBJECT(_simpleQuizzText->WrappedWidget()),
                                  "key_press_event");
    QuizzAnswerClicked( obj );
  }
  return true;
}


bool
MyLangForm::QuizzAnswerClicked( VDKObject* sender)
{
  char message [255];
  bool correct;

  _quizzes++;

  // first of all we test to see if the answer is for
  // a simple or for a multiple quizz
  if( sender == _simpleQuizzButton ||
      sender == _simpleQuizzText ){
    // it's simple quizz
    // printf("Simple quizz\n");
    correct = (0 == strcoll((_simpleQuizzSense==0)
                            ?_simpleQuizzKey->Get()
                            :_simpleQuizzKey->Name(),
                            _simpleQuizzText->GetChars(0)) );
  } else{
    correct = (sender->Tag == _goodTag);
  }

  if( correct ){
    _goodAnswers++;
    sprintf(message,
            "Bravo ! [ %d (%3.2f%%) good from %d ]",
            _goodAnswers,
            100.0*_goodAnswers/_quizzes,
            _quizzes);
    _simpleQuizzText->Clear();
    showQuizz();
  }else{
    sprintf(message,
            "Sorry ! [ %d (%3.2f%%) good from %d ]",
            _goodAnswers,
            100.0*_goodAnswers/_quizzes,
            _quizzes);
  }
  _sbar->Push(message);
  _pbar->Update(1.0*_goodAnswers/_quizzes);

  if( _answerStepCheck->Checked==true &&  _timerCheck->Checked==true ){
    int tmp = atoi ( _answerStepEntry->Text );
    if( tmp<=0 && tmp>=1000 )
      tmp = atoi( _setup.limitedAnswerNr );
    if( _goodAnswers%tmp==0 && _goodAnswers>0 ){
      // it's time to hide the application
      Close();
    }
  }

  return true;
}


bool
MyLangForm::ReloadClicked( VDKObject*)
{
  loadLangDrillRC(false, _setup.name);
  return true;
}

bool MyLangForm::SelectRow(VDKObject*)
{
  // check if the page SOLUTIONS_PAGE is visible
  if(_book->ActivePage == SOLUTIONS_PAGE ){
    // delete the old rows
    _clistWords->Freeze();
    _clistWords->Clear();

    // load the new rows
    char ** textWords;
    textWords = new char *[2];
    for(int i=0; i<2; i++)
      textWords[i] = new char[255];

    MySection *sect;

    // get the selected lessons
    // first we check that we have at least one Section
    if( _clist->Tuples.size()>0){
      VDKArray<int>& selected = _clist->Selections();
      VDKArray<VDKString> selectedNames;
      int selectedNr = selected.size();
      selectedNames.resize(selectedNr);
      VDKArray<int> realOrder(selectedNr);
      for(int i=0; i<selectedNr; i++){
        selectedNames[i]=(char*)_clist->Tuples[(selectedNr?selected[i]:i)][0];
      }

      for(int i=0, k=0; i<_clist->Size(); i++){
        for(int j=0; j<selectedNr;j++){
          if(_clist->Tuples[i][0] == selectedNames[j]){
            realOrder[k]=i;
            k++;
            break;
          }
        }
      }

      // compute the total number of keys available
      // if nothing's selected, than use the first entry
      bool color = false;

      // we have to order correctly the Tuples
      for(int i=0; i<(selectedNr?selectedNr:1); i++){
        sect =
          _config->Section((char*)_clist->Tuples[(selectedNr?realOrder[i]:i)][0]);
        for(int j=0; j< sect->Number(); j++){
          sprintf(textWords[0],sect->operator[](j)->Name());
          sprintf(textWords[1],sect->operator[](j)->Get());
          // Doesn't work anymore because it changes the whole list color
          _clistWords->NormalBackground = color ? clIvory : clWhite;
          _clistWords->AddRow( textWords );
          color = !color;
        }
      }
    }

    _clistWords->Thaw();

    for(int i=0; i<2; i++)
      delete [] textWords[i];
    delete [] textWords;

    _clistWordsUpdated = true;
  }else{
    _clistWordsUpdated = false;
  }

  return true;
}


bool
MyLangForm::SenseToggled(VDKObject* s)
{
  VDKRadioButtonGroup* sender = dynamic_cast<VDKRadioButtonGroup*>(s);
  if(sender==NULL){
    _sbar->Push("<Sense Toggled> was not triggered by a RadioButton !");
    return true;
  }

  // seccond button (Selected == 1) means "English > Japanese"
  _setup.sense = sender->Selected;
  return true;
}


bool
MyLangForm::SwitchPage( VDKObject*)
{
  if( _clistWordsUpdated == false ){
    SelectRow(NULL);
  }

  return true;
}


bool
MyLangForm::StartClicked( VDKObject*)
{
  _quizzes = 0;
  _goodAnswers = 0;

  _sbar->Push("Starting quizzes. All counters set to 0.");
  _pbar->Update(1.0);

  if(changeAnswerButtons())
    showQuizz();

  if(_book->ActivePage != QUIZZES_PAGE &&
     _book->ActivePage != SIMPLEQUIZZ_PAGE )
    _book->ActivePage = QUIZZES_PAGE;
  return true;
}

bool
MyLangForm::TimerTick( VDKObject*)
{
  _elapsedTime--;
  if( _elapsedTime<=0 ){
    _timer->Stop();
    _timerBar->Value = 0;
    // here we should bring to window to top
    if( _hideCheck->Checked == true )
      Show();
    else
      Iconized = false;
    Raise();
    _elapsedTime = 0;
    _timerRunning = false;
  }else{
    _timerBar->Value = 1 - (float)_elapsedTime/_timerCicle;
  }

  return true;
}


//////////////////////////////
// The VDKApplication
//////////////////////////////
class MyLangDrillApp: public VDKApplication
{
  int   *_argc;
  char **_argv;
public:
  MyLangDrillApp(int *argc, char *argv[])
    :VDKApplication(argc, argv){
    _argc = argc;
    _argv = argv;

    // set a 10 sec timed garbage collector
    SetGarbageCollection(10000);
  };

  ~MyLangDrillApp(){};

  void Setup(){
    MainForm = new MyLangForm(this, _argc, _argv);
    MainForm->Setup();
    MainForm->Show();
  }
};

//////////////////////////////
// The main function
//////////////////////////////
int main (int argc, char *argv[])
{

  // These should be set before running the program, not
  // hardcoded or set by program.
  //
  // setenv("LC_CTYPE", "ja_JP.ujis", 1);
  // setenv("LC_MESSAGES", "C", 1);

  gtk_set_locale ();

  //  setlocale(LC_CTYPE, "ja_JP.ujis");
  //  setlocale(LC_MESSAGES, "C");

  MyLangDrillApp app(&argc, argv);
  app.Run();
  return 0;
}
