#include "sxfer.h"

#include "attributes.h"
#include "auth_area.h"
#include "defines.h"
#include "fileinfo.h"
#include "fileutils.h"
#include "index.h"
#include "index_file.h"
#include "log.h"
#include "misc.h"
#include "schema.h"
#include "sresponse.h"
#include "strutil.h"

#define DATA_FILE_TEMPLATE       "%s/%s.XXXXXX"

typedef struct xfer_class_struct
{
  class_struct *class;
  dl_list_type attr_list;
} xfer_class_struct;
 
typedef struct xfer_arg_struct
{
  auth_area_struct *auth_area;
  char             *serial_no;
  dl_list_type     xfer_class_list;
} xfer_arg_struct;

static char data_dir[MAX_LINE];
static char data_file[MAX_LINE];
static FILE *data_fp             = NULL;

static int
create_data_file_record PROTO((auth_area_struct *aa,
                               dl_list_type     *response));

static void
create_data_file_line PROTO((FILE *fp,
                             char *tag,
                             char *value));

static int destroy_xfer_class_data PROTO((xfer_class_struct *xclass));

static int destroy_xfer_arg_data PROTO((xfer_arg_struct *xarg));

static void xfer_split_av PROTO((char *str,
                                 char *attr,
                                 char *value));

static xfer_arg_struct *
xfer_parse_args PROTO((char             *str,
                       auth_area_struct *aa));

static int class_in_xfer_arg PROTO((class_struct    *class,
                                    xfer_arg_struct *xs));


/* ------------------- LOCAL FUNCTIONS -------------------- */


/* create_data_file_record: This function maps an RWhois server
   response into a data file record */
static int
create_data_file_record(aa, response)
  auth_area_struct *aa;
  dl_list_type     *response;
{
  int         not_done;
  int         first_str        = TRUE;
  static int  first_record     = FALSE;
  char        class[MAX_LINE];
  char        tag[MAX_LINE];
  char        value[MAX_LINE];
  char        *str             = NULL;
  static char *old_class       = NULL;

  if (dl_list_empty(response))
  {
    return(FALSE);
  }

  not_done = dl_list_first(response);
  while (not_done)
  {
    str = dl_list_value(response);
    if (!get_tuple(class, tag, value, str))
    {
      return(FALSE);
    }

    /* Check the first line in the response */
    if (first_str)
    {
      /* Check for a new class */
      if (!old_class || !STR_EQ(class, old_class))
      {
        if (old_class)
        {
          /* Close data file for the old class */
          release_file_lock(data_file, data_fp);
          free(old_class); old_class = NULL;
          
          first_record = FALSE;
        }

        old_class = NEW_STRING(class);

        bzero((char *) data_dir, MAX_LINE);
        sprintf(data_dir, "%s/%s", aa->data_dir, class);

        bzero((char *) data_file, MAX_LINE);
        create_filename(data_file, DATA_FILE_TEMPLATE, data_dir);
        strcat(data_file, ".txt");

        if (!directory_exists(data_dir))
        {
          mkdir(data_dir, 493);
        }

        /* Open data file for the new class */
        if ((data_fp = get_file_lock(data_file, "w", 60)) == NULL)
        {
          log(L_LOG_ERR, SECONDARY,
              "create_data_file_record: could not open data file %s: %s",
              data_file, strerror(errno));
          return(FALSE);
        }
      }

      /* Except for the first record, print the record
         separator before printing the data file record */
      if (!first_record)
      {
        first_record = TRUE;
      }
      else
      {
        fprintf(data_fp, "---\n");
      }

      first_str = FALSE;
    }

    /* Print data file line */
    create_data_file_line(data_fp, tag, value);

    not_done = dl_list_next(response);
  }

  return(TRUE);
}


/* create_data_file_line: This function creates a data file line */
static void
create_data_file_line(fp, tag, value)
  FILE *fp;
  char *tag;
  char *value;
{
  if (STR_EQ(tag, "Class-Name"))
  {
    return;
  }

  fprintf(fp, "%s:%s\n", tag, value);
}


/* destroy_xfer_class_data: This function frees a
   xfer_class_struct structure */
static int
destroy_xfer_class_data(xclass)
  xfer_class_struct *xclass;
{
  if (!xclass)
  {
    return(TRUE);
  }

  dl_list_destroy(&(xclass->attr_list));
  free(xclass);

  return(TRUE);
}


/* destroy_xfer_arg_data: This function frees a xfer_arg_struct
   structure */
static int
destroy_xfer_arg_data(xarg)
  xfer_arg_struct *xarg;
{
  if (!xarg)
  {
    return(TRUE);
  }

  if (xarg->serial_no)
  {
    free(xarg->serial_no);
  }

  dl_list_destroy(&(xarg->xfer_class_list));

  free(xarg);

  return(TRUE);
}


/* xfer_split_av: This function parses an attribute=value string */
static void 
xfer_split_av(str, attr, value)
  char *str;
  char *attr;
  char *value;
{
  char *eq = NULL;

  eq = strchr(str, '=');
  
  if (!eq)
  {
    strcpy(value, str);
    attr[0] = '\0';
    return;
  }

  *eq++ = '\0';
  strcpy(attr, str);
  trim(attr);

  strcpy(value, eq);
  trim(value);
}


/* xfer_parse_args: This function parses the xfer-arg parameter for
   partial replication */
static xfer_arg_struct * 
xfer_parse_args(str, aa)
  char             *str;
  auth_area_struct *aa;
{
  xfer_arg_struct   *xs;
  xfer_class_struct *cur_xclass = NULL;
  class_struct      *class;
  attribute_struct  *a;
  int               argc;
  char              **argv;
  char              attr[MAX_LINE];
  char              value[MAX_LINE];
  int               i;

  split_arg_list(str, &argc, &argv);

  if (argc < 1)
  {
    free_arg_list(argv);
    return(NULL);
  }

  xs = xcalloc(1, sizeof(*xs));
  dl_list_default(&(xs->xfer_class_list), FALSE, destroy_xfer_class_data);

  for (i = 0; i < argc; i++)
  {
    xfer_split_av(argv[i], attr, value);

    if (!*attr)
    {
      xs->serial_no = xstrdup(value);
    }
    else
    {
      if (STR_EQ(attr, "class"))
      {
        cur_xclass = xcalloc(1, sizeof(*cur_xclass));
        dl_list_default(&(cur_xclass->attr_list), FALSE, null_destroy_data);
        
        class = find_class_by_name(aa->schema, value);
        if (!class)
        {
          free_arg_list(argv);
          destroy_xfer_class_data(cur_xclass);
          destroy_xfer_arg_data(xs);
          return(NULL);
        }

        cur_xclass->class = class;
        dl_list_append(&(xs->xfer_class_list), cur_xclass);
      }
      else if (STR_EQ(attr, "attr"))
      {
        if (!cur_xclass)
        {
          free_arg_list(argv);
          destroy_xfer_arg_data(xs);
          return(NULL);
        }
        
        a = find_attribute_by_name(class, value);
        if (!a)
        {
          free_arg_list(argv);
          destroy_xfer_arg_data(xs);
          return(NULL);
        }
        dl_list_append(&(cur_xclass->attr_list), a);
      }
    }
  }

  free_arg_list(argv);
  
  return(xs);
}


/* class_in_xfer_arg: This function checks if a class is in
   the xfer-arg parameter for partial replication */
static int
class_in_xfer_arg(class, xs)
  class_struct    *class;
  xfer_arg_struct *xs;
{
  xfer_class_struct *xfer_class;
  class_struct      *c;
  int               not_done;
 
  not_done = dl_list_first(&(xs->xfer_class_list));
 
  while (not_done)
  {
    xfer_class = dl_list_value(&(xs->xfer_class_list));
    c = xfer_class->class;

    if (STR_EQ(c->name, class->name))
    {
      return(TRUE);
    }
 
    not_done = dl_list_next(&(xs->xfer_class_list));
  }

  return(FALSE);
}
 

/* ------------------- PUBLIC FUNCTIONS ------------------- */


/* create_data_files: This function creates data files for a
   slave authority area */
int
create_data_files(aa, server, initial)
  auth_area_struct *aa;
  server_struct    *server;
  int              initial;
{
  int             sockfd;
  int             not_done             = TRUE;
  int             rval                 = FALSE;
  char            directive[MAX_LINE];
  char            *p;
  char            *aa_dir;
  dl_list_type    response;
  xfer_arg_struct *xs;

  if (!aa || !server)
  {
    return(rval);
  }

  aa_dir = get_aa_schema_directory(aa);
  if (!aa_dir)
  {
    aa_dir = get_default_aa_directory(aa);
    if (!aa_dir)
    {
      aa_dir = xstrdup("./");
    }
  }
  
  /* Get lock */
  if (initial)
  {
    release_dot_lock(aa_dir);
  }
  else
  {
    if (dot_lock_exists(aa_dir))
    {
      free(aa_dir);
      return(rval);
    }
  }
  get_dot_lock(aa_dir, 1);

  /* Connect to the master server */
  connect_server(server->addr, server->port, &sockfd);

  bzero((char *) directive, MAX_LINE);
  if (aa->xfer_arg)
  {
    /* Send '-xfer autharea class=classname attr=attrname'
       directive for partial replication */
    p  = NEW_STRING(aa->xfer_arg);
    xs = xfer_parse_args(p, aa);
 
    if (!(xs))
    {
      free(aa_dir);
      return(rval);
    }

    sprintf(directive, "-xfer %s %s\r\n", aa->name, aa->xfer_arg);
  }
  else
  {
    /* Send '-xfer autharea' directive for complete replication */
    sprintf(directive, "-xfer %s\r\n", aa->name);
  }
  send_directive(sockfd, directive);

  if (!directory_exists(aa->data_dir))
  {
    mkdir(aa->data_dir, 493);
  }

  /* Create data files */
  do
  {
    recv_response(stdin, "%xfer", &response);

    if (dl_list_empty(&response))
    {
      not_done = FALSE;
    }
    else
    {
      if (create_data_file_record(aa, &response))
      {
        rval = TRUE;
      }
      dl_list_destroy(&response);
    }
  } while (not_done);

  release_file_lock(data_file, data_fp);

  close(sockfd);

  /* Index data files */
  if ((p = strchr(strrchr(data_file, '/'), '.')))
  {
    p++;
    index_data_files_by_suffix(aa, p);
  }

  /* Release lock */
  release_dot_lock(aa_dir);

  free(aa_dir);
  
  return(rval);
}


/* index_data_files_by_suffix: This function indexes data files
   by suffix */
int
index_data_files_by_suffix(aa, suffix)
  auth_area_struct *aa;
  char             *suffix;
{
  schema_struct   *schema;
  dl_list_type    *class_list;
  class_struct    *class;
  dl_list_type    full_file_list;
  dl_list_type    data_file_list;
  dl_list_type    index_file_list;
  char            *p;
  int             not_done;
  int             rval                       = TRUE;
  xfer_arg_struct *xs;

  if (!aa || !aa->schema || !suffix)
  {
    return(FALSE);
  }

  schema = aa->schema;
  class_list = &(schema->class_list);

  if (dl_list_empty(class_list))
  {
    return(FALSE);
  }

  if (aa->xfer_arg)
  {
    p  = NEW_STRING(aa->xfer_arg);
    xs = xfer_parse_args(p, aa);
 
    if (!(xs))
    {
      return(FALSE);
    }
  }

  /* Iterate through each class */
  not_done = dl_list_first(class_list);
  while (not_done)
  {
    class = dl_list_value(class_list);

    /* Skip if class is not in the xfer-arg parameter for
       partial replication */
    if (aa->xfer_arg)
    {
      if (!class_in_xfer_arg(class, xs))
      {
        not_done = dl_list_next(class_list);
        continue;
      }
    }

    /* Skip if the class doesn't have a directory (and therefore
       nothing was transferred */
    if (!directory_exists(class->db_dir))
    {
      not_done = dl_list_next(class_list);
      continue;
    }
    
    /* Get current data and index files list */ 
    dl_list_default(&full_file_list, FALSE, destroy_file_struct_data);
    get_file_list(class, aa, &full_file_list);

    /* Build new data files list */
    dl_list_default(&data_file_list, FALSE, destroy_file_struct_data);
    if (!build_file_list_by_suffix(&data_file_list, MKDB_DATA_FILE,
                                   class->db_dir, suffix))
    {
      rval = FALSE;
      break;
    }

    dl_list_default(&index_file_list, FALSE, destroy_index_fp_data);
    if (!build_index_list(class, aa, &index_file_list, class->db_dir, NULL))
    {
      rval = FALSE;
      break;
    }

    /* Index new data files */
    if (!index_files(class, aa, &index_file_list, &data_file_list, FALSE, TRUE))
    {
      rval = FALSE;
      break;
    }

    /* Replace current data and index files list with the new list */
    modify_file_list(class, aa, NULL, &full_file_list, NULL,
                     &data_file_list, NULL);

    unlink_file_list(&full_file_list);

    dl_list_destroy(&index_file_list);
    dl_list_destroy(&full_file_list);
    dl_list_destroy(&data_file_list);

    not_done = dl_list_next(class_list);
  }

  dl_list_destroy(&index_file_list);
  dl_list_destroy(&full_file_list);
  dl_list_destroy(&data_file_list);

  return(rval);
}
