/*

$Id: user,v 1.19 2000/02/12 16:22:43 joseph Exp $

*/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include "memleak.h"
#include <string.h>

#include "wimplib.h"

#include "config.h"
#include "err.h"
#include "log.h"
#include "msgs.h"
#include "user.h"
#include "popstar.h"
#include "defines.h"

static const char UserFile[] = TASK_CHOICESDIR".Users";

static user_info *user_vector = NULL;
static int vector_size = 0;
static int user_count = 0;

static WimpMenu *user_menu = NULL;

static void user_construct(user_info *user)
{
  user->lname = user->name = user->password = user->server = user->menuentry = 0;
  user->last = false;
  user->uidl = false;
  user->delete = false; /* default : don't delete mail */
  user->apop = true;
//  user->ipass = false;
  user->autofetch = a_online; /* default : autofetch always */
  user->var = NULL;
  user->maxsize = -1; /* default to as in config file */
  /* (If there's no online variable set, we're always "online") */
}

static void user_destruct(user_info *user)
{
  free(user->lname);
  free(user->name);
//  if (!user->ipass)
  free(user->password);
  free(user->server);
  free(user->menuentry);
  if ( user->var )
    free( user->var );
  user_construct(user);
}

static user_info *user_add()
{
  if (++user_count > vector_size)
  {
    user_info *old     = user_vector;
    WimpMenu  *oldmenu = user_menu;
    int       oldsize  = vector_size;

    if (vector_size)
      vector_size *= 2;
    else
      vector_size = 1;

    if ( user_vector )
      user_vector = realloc( user_vector, vector_size * sizeof(user_info) );
    else
      user_vector = malloc( vector_size * sizeof(user_info) );

    if (!user_vector)
    {
      user_vector = old;
      vector_size = oldsize;
      return 0;
    }

    if ( user_menu )
      user_menu   = realloc( user_menu, sizeof(WimpMenu) + (vector_size-1) * sizeof(WimpMenuItem) );
    else
      user_menu   = malloc( sizeof(WimpMenu) + (vector_size-1) * sizeof(WimpMenuItem) );

    if (!user_menu)
    {
      /* we could realloc user_vector smaller here, but I don't see the point. */
      user_menu   = oldmenu;
      vector_size = oldsize;
      return 0;
    }
    if ( oldsize == 0 )
    {
      strcpy( user_menu->title, "User" );
      user_menu->title_fg    = 7; /* black */
      user_menu->title_bg    = 2; /* greyey :-) */
      user_menu->work_fg     = 7;
      user_menu->work_bg     = 0;
      user_menu->item_width  = 4 * 16;
      user_menu->item_height = 0;
      user_menu->gap	     = 0;
      user_menu->items[0].flags = WimpMenuItem_Last;
      user_menu->items[0].icon_flags = WimpIcon_Text | WimpIcon_VCentred;
      *user_menu->items[0].icon_data.t = 0;
      user_menu->items[0].submenu_or_window = (void *) -1;

    }
    frontend_usersmenu( user_menu );
  }
  return user_vector + (user_count - 1);
}

/* Removes most recently added user - used when we can't fully parse the user file line for the user we just added */
static void user_remove()
{
  user_destruct(user_vector + --user_count);
}

static void user_add_to_menu( WimpMenu *menu, const char *name )
{
  /* we know we have space in the menu, as user_add() makes sure and is always called before us */
  int item;
  const unsigned int itemcolour = (WimpIcon_FGColour*7) | (WimpIcon_BGColour*0);
  WimpMenuItem *entry;
  int len;

  for ( item = 0; !(menu->items[item].flags & WimpMenuItem_Last); item++ );

  /* item now points at last menu entry */
  menu->items[item++].flags &= ~WimpMenuItem_Last; /* clear flag from previous last entry */
  if ( menu->item_height == 0 )
  {
    /* special case for the first time we're called after the menu is created */
    /* height is set to zero by user_add when the menu is created */
    menu->item_height = 44;
    item = 0;
  }

  entry = &menu->items[item];

  entry->flags              = WimpMenuItem_Last;
  entry->submenu_or_window  = (void *) -1;
  entry->icon_flags         = WimpIcon_Text | WimpIcon_VCentred | itemcolour;

  len = strlen( name );

  if ( len > 12 )
  {
    entry->icon_flags               |= WimpIcon_Indirected;
    entry->icon_data.it.buffer       = (char *) name; /* NB. name must be present for as long as menu entry is */
    entry->icon_data.it.validation   = (char *) -1;
    entry->icon_data.it.buffer_size  = len + 1;
  }
  else if (len < 12)
    memcpy( entry->icon_data.t, name, len + 1 );
  else
    memcpy( entry->icon_data.t, name, 12 ); /* don't copy terminator */
}


static char *strdup( const char *s )
{
  int l = strlen( s ) + 1;
  char *r = malloc( l );
  if ( !r ) return NULL;
  memcpy( r, s, l );
  return r;
}


static char *user_extract( char **start )
{
  char *ptr;

  while ( **start == '\t' ) (*start)++; /* skip any white space */

  for ( ptr = *start; !iscntrl(*ptr) && *ptr != '\t' && *ptr; ptr++ );

  if ( !*ptr )
    /* last field */
    return NULL;

  *ptr++ = 0;
  return ptr; /* return pointer to start of next field. */
}


static const char *itoa (int i)
{
  static char num[12];
  sprintf (num, "%d", i);
  return num;
}

#include <swis.h>

void user_init( void )
{
  user_info *user;
  FILE *fp = fopen(UserFile, "r");
  int overlong = 0;
  int num = 0;
  char line[512];

  if (!fp)
  {
    xsyslogf(log_NAME, log_FatalError, "Unable to open users file (%s)", UserFile);
    err_complain(0, msgs_qlookup("BadUser"));
    return;
  }

  while ( fgets( line, sizeof line, fp ) )
  {
    int col = 0; /* number of columns successfully read */
    char *ptr = line + strlen(line) - 1, *start, *next; 

    if ( overlong )
    {
      if (*ptr == '\n')
        overlong = 0;
      continue; /* skip remainder of an overlong line */
    }
    else
      ++num;

    if ( *ptr != '\n' && ptr == (line + sizeof line - 2) )
    {
      overlong = 1;
      xsyslogf(log_NAME, log_ClientInfo, "Overlong line in users file, line %d", num);
      err_complain(0, msgs_qlookup("LongUserLine"));
      continue;
    }

    if ( *ptr == '\n' )
      *ptr = 0; /* remove new line */

    start = line + strspn (line, " \t");

    if ( *start == '#' || !*start )
      continue; /* skip comments, null line and blank lines */

    if (start != line + strspn (line, " "))
    {
      xsyslogf(log_NAME, log_ClientInfo, "Incomplete or aborted line in users file, line %d", num);
      err_complain(0, msgs_lookup1("CorruptUserLine", itoa (num)));
      continue;
    }

    user = user_add();
    if (!user)
    {
      E(msgs_nomem());
      fclose( fp );
      return;
    }
    user_construct(user); /* clear to empty / defaults */
    next = start;
    while ( next && (start = next, next = user_extract( &start ), *start) )
    {
      const char *t = "";
      /* loop until next is null (end of line),
       * or we get an empty string (end of line with white space before it) */
      switch ( ++col )
      {
        case 1: t = user->lname    = strdup( start ); break;
        case 2: t = user->name     = strdup( start ); break;
        case 3: t = user->password = strdup( start ); break;
        case 4: t = user->server   = strdup( start ); break;
        case 5: /* Delete (Y/N) */
          switch (toupper (*start))
          {
          case 'Y': user->delete = 1; break;
          case 'N': user->delete = 0; break;
          default: col = 0; /* abort */
          }
          break;
        case 6: /* Automatic fetch (Y/N) */
          switch (toupper (*start))
          {
          case 'Y': user->autofetch = a_always; break;
          case 'O': user->autofetch = a_online; break;
          case 'N': user->autofetch = a_never; break;
          case '<':
            if (start[strlen(start) - 1] == '>')
            {
              start[strlen(start) - 1] = 0;
              t = user->var = strdup( start + 1 );
              user->autofetch = a_var;
              break;
            }
            /* else abort */
          default: col = 0; /* abort */
          }
          break;
        case 7: /* Use 'last?' (Y/N) */
          switch (toupper (*start))
          {
          case 'Y':
          case 'L': user->last = true;  user->uidl = false; break;
          case 'U': user->last = false; user->uidl = true;  break;
          case 'N': user->last = false; user->uidl = false; break;
          default: col = 0; /* abort */
          }
          break;
        case 8: /* Size (Y/N/Size) */
          switch (toupper (*start))
          {
          case 'Y': user->maxsize = -1; break; /* as config */
          case 'N': user->maxsize =  0; break; /* unlimited */
          default:
            if (isdigit (*start))
            {
              user->maxsize = atoi( start );
              while ( isdigit( *start ) ) start++;
              switch (toupper (*start))
              {
              case 0: break;
              case 'K': user->maxsize *= 1024; break;
              case 'M': user->maxsize *= 1024*1024; break;
              default: col = 0; /* junk after number */
              }
            }
            else
              col = 0; /* abort - too many fields? */
          }
          break;
        case 9: /* APOP (Y/N) */
          switch (toupper (*start))
          {
          case 'Y': user->apop = true; break;
          case 'N': user->apop = false; break;
          default: col = 0; /* abort */
          }
          break;
        default:
          /* ignore surplus fields, for forwards compatibility */
          break;
      } /* switch */
      if ( t == NULL )
        col = 0; /* abort... malloc() failed */
      if ( col == 0 )
        next = NULL; /* abort this line, remove what we have of this user */
    }

    if ( col < 5 ||
         (user->menuentry = malloc( strlen( user->name ) + strlen( user->server ) + 2 ), user->menuentry==NULL) )
    {
      /* incomplete or aborted line (or out of memory...) */
      user_remove();
      xsyslogf(log_NAME, log_ClientInfo, "Incomplete or aborted line in users file, line %d", num);
      err_complain(0, msgs_lookup1("CorruptUserLine", itoa (num)));
    }
    else
    {
      strcpy( user->menuentry, user->name );
      strcat( user->menuentry, "@" );
      strcat( user->menuentry, user->server );
      /* Got at least upto the 'delete' column, use defaults for others for backwards compat. */
      user_add_to_menu( user_menu, user->menuentry);
    }
  }
  fclose(fp);
  return;
}

const user_info *user_get(int index)
{
  return index >= user_count ? 0 : user_vector + (int) index;
}

void user_reload()
{
  int n;

  for (n = 0; n < user_count; ++n)
    user_destruct(user_vector + n);

  free(user_vector);
  free(user_menu);
  user_vector = 0;
  user_menu = 0;
  frontend_usersmenu( user_menu );
  user_count = vector_size = 0;
  user_init();
}
