/* $Id: clock.cc,v 1.13 2003/06/29 03:17:16 bergo Exp $ */

/*

    eboard - chess client
    http://eboard.sourceforge.net
    Copyright (C) 2000-2003 Felipe Paulo Guazzi Bergo
    bergo@seul.org

    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 <iostream>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include "clock.h"
#include "global.h"
#include "tstring.h"

ClockMaster Chronos;

int ChessClock::freeid=1;

ClockMaster::ClockMaster() {
  timeout_on=0;
  timeout_id=-1;
}

void ClockMaster::append(ChessClock *clockp) {
  clocks.push_back(clockp);
  if (!timeout_on) {
    timeout_id=gtk_timeout_add(333,clockmaster_timeout,this);
    timeout_on=1;
  }
}

void ClockMaster::remove(ChessClock *clockp) {
  list<ChessClock *>::iterator li;
  for(li=clocks.begin();li!=clocks.end();li++)
    if ( (*li) == clockp ) {
      clocks.erase(li);
      return;
    }
}

void ClockMaster::update() {
  list<ChessClock *>::iterator it;
  for(it=clocks.begin();it!=clocks.end();it++)
    (*it)->update();
}

gint clockmaster_timeout(gpointer data) {
  ClockMaster *cm;
  cm=(ClockMaster *)data;
  cm->update();
  return 1;
}

// ==================================================================

ChessClock::ChessClock() {
  active=CLK_STOPPED;
  host=0;
  host2=0;
  mirror=0;
  value[0]=value[1]=0;
  t_ref[0]=t_ref[1]=0;
  val_ref[0]=val_ref[1]=0;
  id=freeid++;
  countdownf=1;

  LastWarning.tv_sec = 0;
  LastWarning.tv_usec = 0;

  Chronos.append(this);
}

ChessClock::~ChessClock() {
  active=CLK_STOPPED;
  host=0;
  Chronos.remove(this);
}

void ChessClock::setMirror(ChessClock *dest) {
  mirror=dest;
}

void ChessClock::setClock(int whitesec,int blacksec,int activep,int countdown)
{
  global.debug("ChessClock","setClock");
  countdownf=countdown;

  //  cerr << "setClock " << whitesec << ',' << blacksec << ',' << activep << endl;
  //  cerr << "oldvals = " << value[0] << ',' << value[1] << endl;

  if (whitesec != CLOCK_UNCHANGED)
    value[0]=whitesec;

  if (blacksec != CLOCK_UNCHANGED)
    value[1]=blacksec;

  t_ref[0]=t_ref[1]=time(0);
  val_ref[0]=value[0];
  val_ref[1]=value[1];

  //  cerr << "newvals = " << value[0] << ',' << value[1] << endl;

  active=activep;
  if (mirror)
    mirror->setClock(whitesec,blacksec,activep,countdown);
}

void ChessClock::incrementClock(int which, int secs) {
  if (secs==0)
    return;
  if (which==0)
    which = active;
  if (which == -1) {
    value[0]   += secs;
    t_ref[0]   = time(0);
    val_ref[0] = value[0];
  }
  if (which == 1) {
    value[1]   += secs;
    t_ref[1]   = time(0);
    val_ref[1] = value[1];
  }
  if (mirror)
    mirror->incrementClock(which, secs);
}

void ChessClock::setHost(ClockHost *hostp) {
  host=hostp;
}

void ChessClock::setAnotherHost(ClockHost *hostp) {
  host2=hostp;
}

int  ChessClock::getActive() {
  return active;
}

void ChessClock::update() {
  time_t now;
  bool u = false;
  if (host) {
    switch(active) {
    case CLK_WHITE_RUNNING:
      now=time(0);
      value[0]=val_ref[0]+(countdownf?-1:1)*(now-t_ref[0]);
      u = true;
      break;
    case CLK_BLACK_RUNNING:
      now=time(0);
      value[1]=val_ref[1]+(countdownf?-1:1)*(now-t_ref[1]);
      u = true;
      break;
    }
    if (u) {
      host->updateClock();
      if (host2) host2->updateClock();
    }
  }
}

int ChessClock::getValue(int black) {
  return(value[black?1:0]);
}

bool ChessClock::lowTimeWarning(piece mycolor) {
  struct timeval now;
  long gap;

  // only countdown clocks can issue warnings
  if (!countdownf) return false;

  gettimeofday(&now,0);
  
  if (now.tv_sec >= LastWarning.tv_sec) {

    if (now.tv_sec - LastWarning.tv_sec > 10) {
      gap=5000;
    } else {
      gap = 100 * (now.tv_sec - LastWarning.tv_sec);
      gap += (now.tv_usec - LastWarning.tv_usec) / 10000;
    }
    
    if (gap < 100) return false;    

  }

  if ( (active==CLK_WHITE_RUNNING) && 
       (mycolor==WHITE) && (value[0] <= global.LowTimeWarningLimit) ) {
    global.timeRunningOut();
    LastWarning.tv_sec = now.tv_sec;
    LastWarning.tv_usec = now.tv_usec;
    return true;
  }

  if ( (active==CLK_BLACK_RUNNING) && 
       (mycolor==BLACK) && (value[1] <= global.LowTimeWarningLimit) ) {
    global.timeRunningOut();
    LastWarning.tv_sec = now.tv_sec;
    LastWarning.tv_usec = now.tv_usec;
    return true;
  }
 
  return false;
}

TimeControl::TimeControl() {
  mode = TC_NONE;
}

TimeControl & TimeControl::operator=(TimeControl &src) {
  mode = src.mode;
  value[0] = src.value[0];
  value[1] = src.value[1];
  return(*this);
}

int TimeControl::operator==(TimeControl &src) {
  if (mode != src.mode) return 0;
  switch(mode) {
  case TC_SPM: 
    return(value[0] == src.value[0]);
  case TC_XMOVES:
  case TC_ICS:
    return(value[0] == src.value[0] && value[1] == src.value[1]);
  default:
    return 1;
  }
}

int TimeControl::operator!=(TimeControl &src) { return(!(src==(*this))); }

void TimeControl::setSecondsPerMove(int seconds) {
  mode = TC_SPM;
  value[0] = seconds;
}

// the serialization is "/a/b/c" (no quotes), a=mode, b,c=value
void TimeControl::fromSerialization(const char *s) {
  tstring t;
  int i;

  // cope with bookmarks from previous versions which just wrote
  // the secs per move value
  if (s[0]>='0' && s[0]<='9') {
    setSecondsPerMove(atoi(s));
    return;
  }

  t.set(s);
  t.setFail(-1);
  i = t.tokenvalue("/");
  if (i<0 || (i>2 && i!=99) ) {
    mode = TC_NONE;
    return;
  }
  mode = (TimeControlMode) i;
  value[0] = t.tokenvalue("/");
  value[1] = t.tokenvalue("/");
}

void TimeControl::setIcs(int base /* secs */, int increment /* secs */) {
  mode = TC_ICS;
  value[0] = base;
  value[1] = increment;
}

void TimeControl::setXMoves(int nmoves, int nsecs) {
  mode = TC_XMOVES;
  value[0] = nmoves;
  value[1] = nsecs;
}

// make sure dest is not shorter than char[32] to avoid
// trouble
void TimeControl::toXBoard(char *dest, int flags) {
  switch(mode) {
  case TC_NONE:
    dest[0] = 0;
    return;
  case TC_SPM:
    if (flags & TCF_GNUCHESS4QUIRK) {
      if (value[0]%60)
	sprintf(dest,"level 1 %d:%02d",value[0]/60,value[0]%60);
      else
	sprintf(dest,"level 1 %d",value[0]/60);  
    } else {
      sprintf(dest,"st %d",value[0]);
    }
    break;
  case TC_ICS:
    if (value[0]%60)
      sprintf(dest,"level 0 %d:%02d %d",value[0]/60,value[0]%60,value[1]);
    else
      sprintf(dest,"level 0 %d %d",value[0]/60,value[1]);
    break;
  case TC_XMOVES:
    if (value[1]%60)
      sprintf(dest,"level %d %d:%02d 0",value[0],value[1]/60,value[1]%60);
    else
      sprintf(dest,"level %d %d 0",value[0],value[1]/60);
    break;
  }
}

void TimeControl::toString(char *dest) {
  char z[64],y[64];
  switch(mode) {
  case TC_NONE:
    sprintf(dest,_("no time control set"));
    break;
  case TC_SPM:
    TimeControl::secondsToString(z,value[0],true);
    sprintf(dest,_("%s per move"),z);
    break;
  case TC_ICS:
    TimeControl::secondsToString(z,value[0],true);
    TimeControl::secondsToString(y,value[1],true);
    sprintf(dest,_("initial time %s, increment %s"),z,y);
    break;
  case TC_XMOVES:
    TimeControl::secondsToString(z,value[1],true);
    sprintf(dest,_("%d moves in %s"),value[0],z);
    break;
  }
}

bool TimeControl::isRegressive() {
  return(mode == TC_ICS || mode == TC_XMOVES);
}

int TimeControl::startValue() {
  if (mode == TC_NONE || mode == TC_SPM)
    return 0;
  else 
    return(mode==TC_XMOVES?value[1]:value[0]);
}

int TimeControl::stringToSeconds(char *src) {
  tstring t;
  int x, v=0;
  if (src[0] == '-')
    src++;
  t.set(src);
  t.setFail(-1);
  
  x=t.tokenvalue(":hms");
  if (x>=0) {
    v = x;
    x=t.tokenvalue(":hms");
    if (x>=0) {
      v = 60*v + x;
      x=t.tokenvalue(":hms");
      if (x>=0)
	v = 60*v + x;
    }
  }
  return v;
}

void TimeControl::secondsToString(char *dest, int secs, bool units) {
  if (secs < 0) {
    secs = -secs;
    dest[0] = '-';
    ++dest;
  }
  if (secs < 60)
    sprintf(dest,"%d%c",secs, units?'s':0);
  else if (secs < 3600)
    sprintf(dest,"%d%c%02d%c",
	    secs/60,units?'m':':',secs%60,units?'s':0);
  else
    sprintf(dest,"%d%c%02d%c%02d%c",
	    secs/3600,     units?'h':':',
	    (secs%3600)/60,units?'m':':',
	    (secs%3600)%60,units?'s':0);
}

TimeControlEditDialog::TimeControlEditDialog(TimeControl *tc, bool allownone) : 
  ModalDialog(N_("Edit Time Control")) 
{
  GtkWidget *v, *om, *hs, *bhb, *ok, *cancel;
  GtkWidget *h[6], *l[6], *V[3];
  GtkMenu *ddm;
  int i;
  char z[64];

  listener = 0;
  src      = tc;
  local    = (*src);

  if (local.mode == TC_NONE)
    allownone = true;

  gtk_window_set_default_size(GTK_WINDOW(widget),300,150);

  v=gtk_vbox_new(FALSE,0);
  gtk_container_add(GTK_CONTAINER(widget),v);

  om = gtk_option_menu_new();

  ddm = GTK_MENU(gtk_menu_new());
  mi[0] = gtk_menu_item_new_with_label(_("Type: Fixed Time per Move"));
  mi[1] = gtk_menu_item_new_with_label(_("Type: X Moves per Time Period"));
  mi[2] = gtk_menu_item_new_with_label(_("Type: Fischer Clock (ICS-like)"));
  mi[3] = gtk_menu_item_new_with_label(_("Type: Use engine's default setting"));
  for(i=0;i< (allownone ? 4 : 3);i++) {
    gtk_menu_append(ddm, mi[i]);
    gtk_signal_connect(GTK_OBJECT(mi[i]),"activate",
		       GTK_SIGNAL_FUNC(tced_dropmenu),
		       (gpointer) this);
    Gtk::show(mi[i],0);
  }

  gtk_option_menu_set_menu(GTK_OPTION_MENU(om),GTK_WIDGET(ddm));

  gtk_box_pack_start(GTK_BOX(v),om,FALSE,TRUE,0);

  for(i=0;i<3;i++) {
    f[i] = gtk_frame_new(0);
    V[i] = gtk_vbox_new(FALSE,2);
    h[i] = gtk_hbox_new(TRUE,2);
    h[3+i] = gtk_hbox_new(TRUE,2);
    gtk_container_add(GTK_CONTAINER(f[i]),V[i]);
    gtk_container_set_border_width(GTK_CONTAINER(V[i]),4);
    gtk_box_pack_start(GTK_BOX(V[i]),h[i],FALSE,TRUE,2);
    gtk_box_pack_start(GTK_BOX(V[i]),h[i+3],FALSE,TRUE,2);
    Gtk::show(h[i],h[i+3],V[i],0);
  }

  e[0] = gtk_entry_new();
  l[1] = gtk_label_new(_("per move"));
  gtk_box_pack_start(GTK_BOX(h[0]),e[0],TRUE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(h[0]),l[1],FALSE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(v), f[0], FALSE, TRUE, 4);

  e[1] = gtk_entry_new();
  l[2] = gtk_label_new(_("moves in"));
  e[2] = gtk_entry_new();
  l[3] = gtk_label_new(_("(time period)"));
  gtk_box_pack_start(GTK_BOX(h[1]),e[1],TRUE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(h[1]),l[2],FALSE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(h[1+3]),e[2],TRUE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(h[1+3]),l[3],FALSE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(v), f[1], FALSE, TRUE, 2);

  l[4] = gtk_label_new(_("Starting Time:"));
  e[3] = gtk_entry_new();
  l[5] = gtk_label_new(_("Increment:"));
  e[4] = gtk_entry_new();
  gtk_box_pack_start(GTK_BOX(h[2]),l[4],FALSE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(h[2]),e[3],TRUE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(h[2+3]),l[5],FALSE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(h[2+3]),e[4],TRUE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(v), f[2], FALSE, TRUE, 2);

  l[0] = gtk_label_new(_("Times can be given as hh:mm:ss , mm:ss or ss"));
  gtk_box_pack_start(GTK_BOX(v),l[0],FALSE,TRUE,4);

  bhb=gtk_hbutton_box_new();
  gtk_button_box_set_layout(GTK_BUTTON_BOX(bhb), GTK_BUTTONBOX_END);
  gtk_button_box_set_spacing(GTK_BUTTON_BOX(bhb), 5);
  gtk_box_pack_end(GTK_BOX(v),bhb,FALSE,FALSE,4);
  ok=gtk_button_new_with_label    (_("OK"));
  GTK_WIDGET_SET_FLAGS(ok,GTK_CAN_DEFAULT);
  cancel=gtk_button_new_with_label(_("Cancel"));
  GTK_WIDGET_SET_FLAGS(cancel,GTK_CAN_DEFAULT);
  gtk_box_pack_start(GTK_BOX(bhb),ok,TRUE,TRUE,0);
  gtk_box_pack_start(GTK_BOX(bhb),cancel,TRUE,TRUE,0);
  gtk_widget_grab_default(ok);

  hs=gtk_hseparator_new();
  gtk_box_pack_end(GTK_BOX(v),hs,FALSE,TRUE,4);

  gtk_signal_connect(GTK_OBJECT(ok), "clicked",
		     GTK_SIGNAL_FUNC(tced_ok), (gpointer) this);

  setDismiss(GTK_OBJECT(cancel),"clicked");

  Gtk::show(om,GTK_WIDGET(ddm),l[0],l[1],l[2],l[3],l[4],l[5],
	    e[0],e[1],e[2],e[3],e[4],
	    hs,ok,cancel,bhb,v,0);

  switch(local.mode) {
  case TC_SPM:
    gtk_option_menu_set_history(GTK_OPTION_MENU(om), 0);
    TimeControl::secondsToString(z,local.value[0]);
    gtk_entry_set_text(GTK_ENTRY(e[0]),z);
    gshow(f[0]);
    break;
  case TC_XMOVES:
    gtk_option_menu_set_history(GTK_OPTION_MENU(om), 1);
    sprintf(z,"%d",local.value[0]);
    gtk_entry_set_text(GTK_ENTRY(e[1]),z);
    TimeControl::secondsToString(z,local.value[1]);
    gtk_entry_set_text(GTK_ENTRY(e[2]),z);
    gshow(f[1]);
    break;
  case TC_ICS:
    gtk_option_menu_set_history(GTK_OPTION_MENU(om), 2);
    TimeControl::secondsToString(z,local.value[0]);
    gtk_entry_set_text(GTK_ENTRY(e[3]),z);
    TimeControl::secondsToString(z,local.value[1]);
    gtk_entry_set_text(GTK_ENTRY(e[4]),z);
    gshow(f[2]);
    break;
  case TC_NONE:
    gtk_option_menu_set_history(GTK_OPTION_MENU(om), 3);
    break;
  }
}

void TimeControlEditDialog::setUpdateListener(UpdateInterface *ui) {
  listener = ui;
}

void tced_dropmenu(GtkMenuItem *w, gpointer data) {
  TimeControlEditDialog *me = (TimeControlEditDialog *) data;
  GtkWidget *ww = GTK_WIDGET(w);
  
  switch(me->local.mode) {
  case TC_SPM:    gtk_widget_hide(me->f[0]); break;
  case TC_XMOVES: gtk_widget_hide(me->f[1]); break;
  case TC_ICS:    gtk_widget_hide(me->f[2]); break;
  default:
    break;
  }

  if (ww == me->mi[0]) {
    me->local.mode = TC_SPM;
    gshow(me->f[0]);
    gtk_widget_grab_focus(me->e[0]);
  } else if (ww == me->mi[1]) {
    me->local.mode = TC_XMOVES;
    gshow(me->f[1]);
    gtk_widget_grab_focus(me->e[1]);
  } else if (ww == me->mi[2]) {
    me->local.mode = TC_ICS;
    gshow(me->f[2]);
    gtk_widget_grab_focus(me->e[3]);
  } else if (ww == me->mi[3]) {
    me->local.mode = TC_NONE;
  }
}

void tced_ok(GtkWidget *w, gpointer data) {
  char z[128], y[128];

  TimeControlEditDialog *me = (TimeControlEditDialog *) data;

  z[127] = y[127] = 0;
  switch(me->local.mode) {
  case TC_SPM:
    strncpy(z, gtk_entry_get_text(GTK_ENTRY(me->e[0])), 127);
    me->local.setSecondsPerMove( TimeControl::stringToSeconds(z) );
    break;
  case TC_XMOVES:
    strncpy(z, gtk_entry_get_text(GTK_ENTRY(me->e[1])), 127);
    strncpy(y, gtk_entry_get_text(GTK_ENTRY(me->e[2])), 127);
    me->local.setXMoves( atoi(z), TimeControl::stringToSeconds(y) );
    break;
  case TC_ICS:
    strncpy(z, gtk_entry_get_text(GTK_ENTRY(me->e[3])), 127);
    strncpy(y, gtk_entry_get_text(GTK_ENTRY(me->e[4])), 127);
    me->local.setIcs(TimeControl::stringToSeconds(z),
		     TimeControl::stringToSeconds(y) );
    break;
  }
  
  *(me->src) = me->local;
  if (me->listener)
    me->listener->update();
  me->release();
}
