/***************************************
  $Revision: 1.4 $

  Example code: Create

  Status: NOT REVUED, NOT TESTED

  Author:       Chris Ottrey

  +html+ <DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL>
  +html+ </DL>
 
  ******************/ /******************
  Modification History:
        ottrey (01/11/1999) Created.
  ******************/ /******************
  Copyright (c) 1999                              RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/
#include <stdio.h>
#include <strings.h>
#include <glib.h>
#include "defs.h"
#include "mysql_driver.h"

#define DEFAULT_PROP_FILE_NAME ".properties"
#define SLEEP_TIME 10

/*+ String sizes +*/
#define STR_S   63
#define STR_M   255
#define STR_L   1023
#define STR_XL  4095
#define STR_XXL 16383

typedef enum _Line_Type_t {
  LINE_ADD,
  LINE_DEL,
  LINE_ATTRIBUTE,
  LINE_COMMENT,
  LINE_EMPTY,
  LINE_EOF
} Line_Type_t;

typedef struct _Attribute_t { 
  A_Type_t type;
  char *value;
} Attribute_t;

typedef struct _Object_t {
  C_Type_t type;
  GSList *attributes;
} Object_t;

typedef struct _Transaction {
  SQ_connection_t *sql_connection;
  int thread_id;
  C_Type_t class_type;
  int succeeded;
} Transaction_t;

char * const Tables[] = {
  "ac",
  "an",
  "dn",
  "tc",
  "zc",
  "nh",
  "object",
  NULL
}; /* Tables */

#include "UD_inserts.def"

FILE *Scum_file;

static void escape_apostrophes(GString *text) {
  int i;
  for (i=0; i < text->len; i++) {
    if (text->str[i] == '\'') {
      g_string_insert_c(text, i, '\\');
      i++;
    }
  }
} /* escape_apostrophes() */

static void each_attribute_to_string(void *element_data, void *result_ptr) {
  Attribute_t *attr = element_data;
  GString *result = (GString *)result_ptr;
  GString *value = g_string_new(attr->value);

  escape_apostrophes(value);

  g_string_sprintfa(result, "%s:   \t%s\n", DF_get_attribute_name(attr->type), value->str);

} /* each_attribute_to_string() */

static char *object_to_string(const Object_t *obj) {
  GString *result = g_string_sized_new(STR_XL);

  g_slist_foreach(obj->attributes, each_attribute_to_string, result);

  return result->str;
} /* object_to_string() */

static Attribute_t *attribute_new(const char *line) {
  Attribute_t *attr = NULL;
  int type;
  char *colon;
  gchar *token;

  colon = index(line, ':'); 
  if (colon != NULL) {
    if (line[0] =='*') {
      token = g_strndup(line+1, 2);
      type = DF_attribute_code2type(token);
    }
    else {
      token = g_strndup(line, (colon - line));
      type = DF_attribute_name2type(token);
    }

    if (type >= 0) {
      char *n;
      attr = (Attribute_t *)calloc(1, sizeof(Attribute_t)+1);
      attr->type = type;
      attr->value = g_strdup((colon+2));
      /* Remove the tailing \n */
      n = index(attr->value, '\n');
      if (n != NULL) {
        *n = '\0';
      }
      /* Strip the whitespace */
      g_strstrip(attr->value);
    }
    else {
      fprintf(stderr, "ERROR: Bad attribute: %s\n", token);
    }
  }
  else {
    fprintf(stderr, "ERROR: Not an attribute: %s\n", line);
  }

  return attr;
} /* attribute_new() */

static object_free(Object_t *obj) {
  g_slist_free(obj->attributes);
  free(obj);
} /* object_free() */

static Object_t *object_new(const char *line) {
  Object_t *obj = NULL;

  int type;
  char *colon;
  gchar *token;

  colon = index(line, ':'); 
  if (colon != NULL) {
    if (line[0] =='*') {
      token = g_strndup(line+1, 2);
      type = DF_class_code2type(token);
    }
    else {
      token = g_strndup(line, (colon - line));
      type = DF_class_name2type(token);
    }

    if (type >= 0) {
      obj = (Object_t *)calloc(1, sizeof(Object_t)+1);
      obj->attributes = NULL;
      obj->type = type;
    }
    else {
      fprintf(stderr, "ERROR: Object has an invalid class: %s\n", token);
    }
  }
  else {
    fprintf(stderr, "ERROR: No colon found in line: %s\n", line);
  }

  return obj;
} /* object_new() */

static Line_Type_t line_type(const char *line) {
  Line_Type_t result = -1;

  int i;
  char * const *attr_aliases = DF_get_attribute_aliases();
  char attr_code[2];

  if (strncmp(line, "# EOF", 4) == 0) {
    result = LINE_EOF;
  }
  else if (strncmp(line, "ADD", 3) == 0) {
    result = LINE_ADD;
  }
  else if (strncmp(line, "DEL", 3) == 0) {
    result = LINE_DEL;
  }
  else if (strncmp(line, "#", 1) == 0) {
    result = LINE_COMMENT;
  }
  else if (strcmp(line, "\n") == 0) {
    result = LINE_EMPTY;
  }
  else {
    result = LINE_ATTRIBUTE;
  }

  return result;
} /* line_type() */

static rollback(Transaction_t *tr) {
  GString *query = g_string_sized_new(STR_XL);

  int i;
  for (i=0; Tables[i] != NULL; i++) {
    g_string_sprintf(query, "DELETE FROM %s WHERE thread_id=%d", Tables[i], tr->thread_id);
    SQ_execute_query(tr->sql_connection, query->str);
  }

  g_string_free(query, TRUE);
} /* rollback() */

static commit(Transaction_t *tr) {
  GString *query = g_string_sized_new(STR_XL);

  int i;
  for (i=0; Tables[i] != NULL; i++) {
    g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE thread_id=%d", Tables[i], tr->thread_id);
    SQ_execute_query(tr->sql_connection, query->str);
    printf("%s\n", query->str);
  }

  g_string_free(query, TRUE);
} /* commit() */

static void each_attribute_update(void *element_data, void *tr_ptr) {
  my_ulonglong num;
  char *query_fmt;
  Attribute_t *attr = element_data;
  Transaction_t *tr = (Transaction_t *)tr_ptr;

  query_fmt = Insert[attr->type];

  if (strcmp(query_fmt, "") != 0) {
    GString *query = g_string_sized_new(STR_XL);

//    g_string_sprintf(query, "INSERT %s SELECT nh.id, max(object.id), object_type.id, %d FROM nh, object, object_type WHERE nh.value='%s' AND object_type.code='%s' GROUP BY object_type.code", DF_get_attribute_code(attr->type), tr->thread_id, attr->value, DF_get_class_code(tr->class_type));
    g_string_sprintf(query, query_fmt, tr->thread_id, attr->value, DF_get_class_code(tr->class_type));
    printf("%s\n", query->str);

    SQ_execute_query(tr->sql_connection, query->str);
    num = mysql_affected_rows(tr->sql_connection);
    if ((num == -1) || (num == 0)) {
      printf("A ERROR!: %lld\n", num);
      tr->succeeded=0;
    }

    g_string_free(query, TRUE);
  }
} /* each_attribute_update() */

static void transaction_free(Transaction_t *tr) {
  free(tr);
} /* transaction_free() */

static Transaction_t *transaction_new(SQ_connection_t *sql_connection, C_Type_t class_type) {
  Transaction_t *tr = (Transaction_t *)calloc(1, sizeof(Transaction_t));

  if (tr != NULL) {
    tr->sql_connection = sql_connection;
    tr->class_type = class_type;
    tr->thread_id = mysql_thread_id(sql_connection);
    tr->succeeded = 1;
  }

  return tr;
} /* transaction_new() */

static void perform_updates(const Object_t *obj, Transaction_t *tr) {
  char *str;
  GString *query = g_string_sized_new(STR_XL);
  my_ulonglong num;

  if (tr != NULL) { 
  if (tr->sql_connection != NULL) { 
      str = object_to_string(obj);
      g_string_sprintf(query, "INSERT INTO object SET value='%s', thread_id=%d", str, tr->thread_id);
      printf("%s\n", query->str);

      SQ_execute_query(tr->sql_connection, query->str);
      num = mysql_affected_rows(tr->sql_connection);
      if ((num == -1) || (num == 0)) {
        printf("B ERROR!: %lld\n", num);
        tr->succeeded=0;
      }

      g_slist_foreach(obj->attributes, each_attribute_update, tr);

      free(str);
    }
  }

  g_string_free(query, TRUE);
} /* perform_updates() */

static object_process(const Object_t *obj) {
  SQ_connection_t *sql_connection;
  int thread_id;
  Transaction_t *tr = NULL;
  char *str;
  
  //sql_connection = SQ_get_connection("rowan.ripe.net", 3306, "test", "user", "password");
  sql_connection = SQ_get_connection2();

  if (sql_connection) {

    tr = transaction_new(sql_connection, obj->type);

    if (tr != NULL) {
      perform_updates(obj, tr);
      if (tr->succeeded == 1) {
        commit(tr);
      }
      else {
        rollback(tr);
        str = object_to_string(obj);
        fprintf(Scum_file, "Couldn't add;\n%s\n", str);
        free(str);
      }
      transaction_free(tr);
    }

    SQ_close_connection(sql_connection);
  }

} /* object_process() */

static object_discard(Object_t *obj) {
  char *str;

  fprintf(stderr, "ERROR: Bad attribute in object => discard\n");

  str = object_to_string(obj);
  fprintf(stderr, "%s", str);
  free(str);

  object_free(obj);
} /* object_discard() */


int main(int argc, char** argv) {
  char *prop_file_name;
  FILE *f1, *f2;
  GString *current_serial;
  int serial_curr=0;
  int serial_prev=0;
  int i, j;
  char line_buff[STR_XXL];

  Attribute_t *attr;
  Object_t *obj;
  int start_object = 1;

  /* 1. Get the properties file name from argv[1] (Defaults to ".properties" if none specified) */
  if (argc == 2) {
    prop_file_name = (char *)calloc(1, strlen(argv[1])+1 );
    strcpy(prop_file_name,argv[1]);
  } else { 
    prop_file_name = (char *)calloc(1, strlen(DEFAULT_PROP_FILE_NAME)+1 );
    strcpy(prop_file_name, DEFAULT_PROP_FILE_NAME);
  }
  printf("Loading properties from prop_file_name=%s\n", prop_file_name);

  /* 2. Load properties object from prop_file. */
  PR_load(prop_file_name); printf("%s\n", PR_to_string() );

  /* 3. Set the constants. */
  CO_set(); printf("%s\n", CO_to_string() );

  current_serial = g_string_sized_new(STR_L);

  Scum_file = fopen("scum", "w");

  while (1) {
    sleep(SLEEP_TIME);

    serial_prev = serial_curr;

    f1 = fopen("/ncc/db2/serials/current/RIPE.CURRENTSERIAL", "r");
    fscanf(f1, "%d", &serial_curr);
    fclose(f1);

    f1 = fopen("/ncc/db2/serials/current/RIPE.OLDESTSERIAL", "r");
    fscanf(f1, "%d", &serial_prev);
    fclose(f1);

    if (serial_prev != 0) {
      for (j=serial_prev; j < serial_curr; j++) { 
        g_string_sprintf(current_serial, "/ncc/db2/serials/current/RIPE.%d", j);
        f2 = fopen((char *)current_serial->str, "r");
        while (fgets(line_buff, STR_XL, f2) != NULL) {
          switch (line_type(line_buff)) {
            case LINE_ATTRIBUTE:
              if (start_object == 1) {
                start_object = 0;
                obj = object_new(line_buff);
              }
              if (obj != NULL) {
                attr = attribute_new(line_buff);
                if (attr != NULL) {
                  obj->attributes = g_slist_append(obj->attributes, attr);
                }
                else {
                  object_discard(obj);
                  obj = NULL;
                }
              }
            break;

            case LINE_ADD:
            break;

            case LINE_DEL:
              goto there;
            break;

            case LINE_COMMENT:
            break;

            case LINE_EOF:
            break;

            case LINE_EMPTY:
              if (obj != NULL) {
                object_process(obj);
                object_free(obj);
                obj = NULL;
              }
              start_object=1;
            break;

            default:
              fprintf(stderr, "ERROR: Bad line type\n");
          } /* switch */
        }
there:
        fclose(f2);

        if (obj != NULL) {
          object_process(obj);
          object_free(obj);
          obj = NULL;
        }
        start_object=1;
      }
    }
  }
  
  fclose(Scum_file);

  g_string_free(current_serial, TRUE);

  return(0);

} /* main() */
