/*==========================================================================*\
 |  This file is part of setiherder (http://www.sackheads.org/setiherder/)  |
 |  which was written by Brian L. Naylor <brian+seti@sackheads.org> and is  |
 |  under the GNU General Public License.  You should have recieved a copy  |
 |  of the GPL with this source code.  If not, we'd like to know about it.  |
\*--------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>     /* memcpy */
#include <gtk/gtk.h>

#include "shclients.h"
#include "shproc.h"
#include "shwin.h"
#include "shutil.h"
#include "shnet.h"

/*-------------------------------------------------------------------------*\
 | GLOBALS                                                                 |
\*-------------------------------------------------------------------------*/
sh_client_t sh_client_list[SH_MAX_CLIENTS];
int         sh_client_idx;

/*-------------------------------------------------------------------------*\
 | FUNCTIONS                                                               |
\*-------------------------------------------------------------------------*/
sh_rc sh_client_new(int *index)
{
  sh_rc rc;

  /* this is the ONLY time this should ever happen! */
  sh_client_idx++;   

  rc = sh_client_init(&sh_client_list[sh_client_idx]);

  if(rc == SH_SUCCESS)
  {
    (*index) = sh_client_idx;
  }
  else
  {
    sh_client_idx--;
    (*index) = -1;
    return SH_ERR_GENERIC;
  }
  return SH_SUCCESS;
}

sh_rc sh_client_init(sh_client_t *newbie)
{
  newbie->index        = sh_client_idx;
  newbie->hostname     = NULL;
  newbie->seticmd      = NULL;
  newbie->account      = NULL;
  newbie->platform     = NULL;
  newbie->version      = NULL;
  newbie->coords       = NULL;
  newbie->recdate      = NULL;
  newbie->source       = NULL;
  newbie->basefreq     = NULL;
  newbie->autores      = SH_DEFAULT_AUTORES;
  newbie->delay        = SH_DEFAULT_DELAY;
  newbie->stop_next    = 0;
  newbie->died         = 0;
  newbie->user_stopped = 1; /* prevents "auto-start" :) */
  newbie->state        = SH_DEFAULT_STATE;
  newbie->completed    = SH_DEFAULT_COMPLETED;
  newbie->dop_shift_rt = SH_DEFAULT_DOP_SHIFT_RT;
  newbie->last_shift   = SH_DEFAULT_LAST_SHIFT;
  newbie->fft_len      = SH_DEFAULT_FFT_LEN;
  newbie->units_done   = SH_DEFAULT_UNITS_DONE;
  newbie->cpu_time     = SH_DEFAULT_CPU_TIME;
  newbie->pid          = SH_DEFAULT_PID;
  newbie->ttyfp        = NULL;
  newbie->ptyfp        = NULL;
  newbie->iotag        = 0;
  newbie->expanded     = NULL;
  newbie->task         = NULL;
  newbie->spawn_method = SH_SPAWN_CMD;
  newbie->detach       = 0;
  newbie->port         = SH_NET_DEFAULT_PORT;
  newbie->passwd       = NULL;
  return SH_SUCCESS;
}

void sh_client_free(int idx)
{
  sh_client_t *sc; 

  sc = &sh_client_list[idx];

  if(sc->hostname != NULL)
  {
    free(sc->hostname);
    sc->hostname = NULL;
  }
  if(sc->seticmd != NULL)
  {
    free(sc->seticmd);
    sc->seticmd = NULL;
  }
  if(sc->account != NULL)
  {
    free(sc->account);
    sc->account = NULL;
  }
  if(sc->expanded != NULL)
  {
    gtk_widget_destroy(sc->expanded->toplevel);
    free(sc->expanded);
  }
  if(sc->task != NULL)
  {
    free(sc->task);
    sc->task = NULL;
  }
  if(sc->passwd != NULL)
  {
    free(sc->passwd);
    sc->passwd = NULL;
  }
}

sh_rc sh_client_del(int idx)
{
  sh_client_t *sc; 
  int          i;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_del() BAD INDEX!\n");
    return SH_ERR_GENERIC;
  }

  sc = &sh_client_list[idx];
 
  if(sc->pid > 0) {
    if(sh_client_stop(sh_client_get(idx)) != SH_SUCCESS)
    {
      dbg(DBG_ERROR,"Runaway client!  Save yourself!  Women and children first!!\n");
    }
  }
 
  sh_client_free(idx);
  
  /*
   * because we expect the indexes of these client records to match
   * the clist row number, we now need to renumber the list.
   */
  for(i=idx; i<sh_client_idx; i++)
  {
    memcpy((void *)&sh_client_list[i], (void *)&sh_client_list[i+1],
           sizeof(sh_client_t));
    sh_client_list[i].index--;
  }
  sh_client_idx--;

  return SH_SUCCESS;
}

sh_client_t *sh_client_get(int idx)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_get() BAD INDEX REQUEST! (%d)\n", idx);
    return NULL;
  }
  return(&sh_client_list[idx]);
}


sh_rc sh_client_start(sh_client_t *cli)
{
  int rc;
  extern void input_callback(gpointer, gint, GdkInputCondition);
  
  cli->pid = 0;

  if(cli->spawn_method == SH_SPAWN_NET)
  {
    if(shnet_connect(cli) != SH_SUCCESS)
    {
      dbg(DBG_ERROR, "Error starting client %d (%s)\n");
      cli->state = SH_STATE_STOPPED;
      cli->died  = 0;  /* something is wrong, don't restart */
      return SH_ERR_CHILD;
    }
  }
  else {
    if((rc = shproc_client_spawn(cli)) != SH_SUCCESS)
    {
      dbg(DBG_ERROR, "Error spawning child! (client %d (%s), rc=%d)\n",
          cli->index, cli->hostname, rc);
      cli->state = SH_STATE_STOPPED;
      cli->died  = 0;  /* something's wrong, don't restart */
      return SH_ERR_CHILD;
    }
    if(cli->pid == SH_DEFAULT_DEAD_PID) /* child already exited (parent sleeps) */
    {
      dbg(DBG_ERROR, "shclient_start(): child already exited!\n");
      cli->state = SH_STATE_CRASHED;
      cli->died  = 0;  /* something's wrong..  don't restart automatically */
      return SH_ERR_CHILD;
    }
    
    cli->iotag = gdk_input_add(fileno(cli->ptyfp),
                               GDK_INPUT_READ, input_callback,
                               (gpointer)&cli->index);
  }
  cli->state = SH_STATE_WORKING;
  cli->died  = 0;

  return SH_SUCCESS;
}

sh_rc sh_client_stop(sh_client_t *cli)
{
  if(cli->pid > 0)
  {
    cli->died  = 0;
    cli->state = SH_STATE_STOPPED;
    if(cli->spawn_method == SH_SPAWN_NET)
    {
      shnet_close(cli);
    }
    else 
    {
      shproc_client_terminate(cli);
    }
  }
  return SH_SUCCESS;
}


sh_rc sh_client_set_hostname(int idx, char *hostname)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_hostname() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
  
  if(sc->hostname != NULL)
    free(sc->hostname);
  
  if((sc->hostname = (char *)malloc(strlen(hostname)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_hostname() failed to allocate %d bytes.\n",
           strlen(hostname)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->hostname, hostname);
  
  /* update */
  shwin_status_change(idx);
  shwin_details_change(idx);

  return SH_SUCCESS;
}

sh_rc sh_client_set_seticmd(int idx, char *seticmd)
{
   sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_seticmd() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
  
  if(sc->seticmd != NULL)
    free(sc->seticmd);
  
  if((sc->seticmd = (char *)malloc(strlen(seticmd)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_seticmd() failed to allocate %d bytes.\n",
           strlen(seticmd)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->seticmd, seticmd);
  return 0;
}

sh_rc sh_client_set_account(int idx, char *account)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_account() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->account != NULL)
    free(sc->account);
  
  if((sc->account = (char *)malloc(strlen(account)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_account() failed to allocate %d bytes.\n",
           strlen(account)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->account, account);

  /* update */
  shwin_status_change(idx);

  return 0;
}

sh_rc sh_client_set_platform(int idx, char *platform)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_platform() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->platform != NULL)
    free(sc->platform);
  
  if((sc->platform = (char *)malloc(strlen(platform)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_platform() failed to allocate %d bytes.\n",
           strlen(platform)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->platform, platform);
  
  /* update */
  shwin_details_change(idx);
  
  return 0;
}

sh_rc sh_client_set_version(int idx, char *version)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_version() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->version != NULL)
    free(sc->version);
  
  if((sc->version = (char *)malloc(strlen(version)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_version() failed to allocate %d bytes.\n",
           strlen(version)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->version, version);
  
  /* update */
  shwin_details_change(idx);
  
  return 0;
}

sh_rc sh_client_set_coords(int idx, char *coords)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_coords() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->coords != NULL)
    free(sc->coords);
  
  if((sc->coords = (char *)malloc(strlen(coords)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_coords() failed to allocate %d bytes.\n",
           strlen(coords)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->coords, coords);
  
  /* update */
  shwin_details_change(idx);
  
  return 0;
}

sh_rc sh_client_set_recdate(int idx, char *recdate)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_recdate() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->recdate != NULL)
    free(sc->recdate);
  
  if((sc->recdate = (char *)malloc(strlen(recdate)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_recdate() failed to allocate %d bytes.\n",
           strlen(recdate)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->recdate, recdate);
  
  /* update */
  shwin_details_change(idx);
  
  return 0;
}

sh_rc sh_client_set_source(int idx, char *source)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_source() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->source != NULL)
    free(sc->source);
  
  if((sc->source = (char *)malloc(strlen(source)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_source() failed to allocate %d bytes.\n",
           strlen(source)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->source, source);
  
  /* update */
  shwin_details_change(idx);
  
  return 0;
}

sh_rc sh_client_set_basefreq(int idx, char *basefreq)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_basefreq() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->basefreq != NULL)
    free(sc->basefreq);
  
  if((sc->basefreq = (char *)malloc(strlen(basefreq)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_basefreq() failed to allocate %d bytes.\n",
           strlen(basefreq)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->basefreq, basefreq);
  
  /* update */
  shwin_details_change(idx);
  
  return 0;
}

sh_rc sh_client_set_completed(int idx, float percent)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_completed() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
  sc->completed = percent;
  
  /* inform gui */ 
  shwin_status_change(idx);
  shwin_details_change(idx); 

  return SH_SUCCESS;
}

sh_rc sh_client_increment_work(int idx)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_increment_work() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].units_done++;
  shwin_increment_session_work();  
  return SH_SUCCESS;
}

sh_rc sh_client_set_autores(int idx, int flag)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_autores() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].autores = flag;

  return SH_SUCCESS;
}

sh_rc sh_client_set_delay(int idx, int value)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_delay() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].delay = value;

  return SH_SUCCESS;
}

sh_rc sh_client_set_stop_next(int idx, int value)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_stop_next() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].stop_next = value;

  return SH_SUCCESS;

}

sh_rc sh_client_set_died(int idx, int flag)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_died() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].died = flag;
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_user_stopped(int idx, int flag)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_user_stopped() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].user_stopped = flag;
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_state(int idx, int state)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_state() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].state = state;
  
  /* update */
  shwin_status_change(idx);
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_dop_shift_rt(int idx, float rt)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_dop_shift_rt() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].dop_shift_rt = rt;
  
  /* update */
  shwin_details_change(idx);
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_last_shift(int idx, float last)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_last_shift() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].last_shift = last;

  /* update */
  shwin_details_change(idx);
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_fft_len(int idx, float len)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_fft_len() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].fft_len = len;
  
  /* update */
  shwin_details_change(idx);
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_units_done(int idx, int units)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_units_done() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].units_done = units;
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_cpu_time(int idx, float cpu)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_cpu_time() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].cpu_time = cpu;
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_task(int idx, char *task)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_task() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
 
  if(sc->task != NULL)
    free(sc->task);
  
  if((sc->task = (char *)malloc(strlen(task)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_task() failed to allocate %d bytes.\n",
           strlen(task)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->task, task);
  
  /* update */
  shwin_details_change(idx);
  
  return 0;
}

sh_rc sh_client_set_spawn_method(int idx, int value)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_spawn_method() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].spawn_method = value;
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_detach(int idx, int value)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_detach() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].port = value;
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_port(int idx, int value)
{
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_port() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  sh_client_list[idx].port = value;
  
  return SH_SUCCESS;
}

sh_rc sh_client_set_passwd(int idx, char *passwd)
{
  sh_client_t *sc;
  
  if( (idx < 0) || (idx > sh_client_idx) )
  {
    dbg(DBG_ERROR,"sh_client_set_passwd() BAD INDEX! (%d)\n", idx);
    return SH_ERR_GENERIC;
  }
  
  sc = &sh_client_list[idx];
  
  if(sc->passwd != NULL)
    free(sc->passwd);
  
  if((sc->passwd = (char *)malloc(strlen(passwd)+1)) == NULL)
  {
    dbg(DBG_ERROR,"sh_client_set_passwd() failed to allocate %d bytes.\n",
           strlen(passwd)+1);
    return SH_ERR_MEM;
  }
  strcpy(sc->passwd, passwd);
  
  return SH_SUCCESS;
}

