#include <glib.h>
#include <stdio.h>
#include <strings.h>
#include <glib.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>

#include "nh.h"
#include <stubs.h>

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



#define get_min_range(prange, sql_connection) get_range(-1, prange, sql_connection)
static long get_range(long nic_id, range_t *prange, SQ_connection_t *sql_connection);
static long update_range(long range_id, range_t *p_newrange, SQ_connection_t *sql_connection);
static long create_range(range_t *p_range, SQ_connection_t *sql_connection);

/************************************************************
* int NH_convert()                                          *
*                                                           *
* Converts space & nic_id into a database nic-handle        *
*                                                           *
*                                                           *
* Returns:                                                  *
* The size of the nic_handle in characters                  *
*                                                           *
************************************************************/
int NH_convert(char *nic, nic_handle_t *nh_ptr)
{
  /* Check for special cases */
  /* Is is and AUTO nic-handle ? */
  if(nh_ptr->nic_id == AUTO_NIC_ID) return(-1);
  if(nh_ptr->space) nic+=sprintf(nic, "%s", nh_ptr->space);
  /* No nic-id ? */
  if(nh_ptr->nic_id != NULL_NIC_ID) nic+=sprintf(nic, "%ld", nh_ptr->nic_id);
  /* No source ? */
  if (nh_ptr->source) sprintf(nic, "%s", nh_ptr->source);
  return(1);
}

/************************************************************
* int NH_parse()                                            *
*                                                           *
* Parse a nic handle as supplied by DBupdate                *
* The format is: <space>[<nic_id>|*][SOURCE]                *
* Also extracts nic_id and space for regular nic-handles    *
*                                                           *
*                                                           *
* Returns:                                                  *
* >0 - success                                              *
*  0 - AUTO NIC                                             *
* -1  - error (not defined and processed yet)               *
*                                                           *
************************************************************/
int NH_parse(char *nic, nic_handle_t **nh_ptr_ptr)
{
char *ptr;
int res = 1;
nic_handle_t *nh_ptr;

     if(!(nh_ptr=calloc(1, sizeof(nic_handle_t)))) die;
     
     ptr=nic;	

     /* extract space */
     while(isalpha((int)*ptr))ptr++;
     if(!(nh_ptr->space=malloc(ptr-nic+1))) die;
     strncpy(nh_ptr->space, nic, ptr-nic); *(nh_ptr->space+(ptr-nic))='\0';

     /* If there are no digits, then this is no nic-hdl */
     /* We reserve NULL_NIC_ID for such pretty identifiers */
     if(*ptr == '\0') {
       nh_ptr->nic_id=NULL_NIC_ID;
       nh_ptr->source=NULL;
     }
     else {
       /* Check if it is and AUTO nic */
       if (*ptr == '*') {
	       /* For AUTO nic_id we reserve AUTO_NIC_ID */
	       nh_ptr->nic_id=AUTO_NIC_ID;
	       res=0;
	       ptr++;
       } else {
         nic=ptr;
	 /* convert digits (if any) and store first invalid characted in ptr */
         nh_ptr->nic_id=(int)strtol(nic, &ptr, 10);
	 /* Check if there were any digits at all */
	 if(ptr == nic) nh_ptr->nic_id=NULL_NIC_ID;
       }
       /* check if there is any suffix */
       if (*ptr == '\0') nh_ptr->source=NULL;
       /* Copy suffix into source */
       else {
	 if(!(nh_ptr->source=malloc(strlen(ptr)+1))) die;
	 strcpy(nh_ptr->source, ptr);
       }
     } 
     *nh_ptr_ptr=nh_ptr;
     return(res);
}



/************************************************************
* int NH_check()                                            *
*                                                           *
* Check a NIC handle in the repository                      *
*                                                           *
*                                                           *
* Returns:                                                  *
*  1 - success                                              *
*  0 - error(nic_id exists or space is fully occupied)      *
* -1 - error (f.e. more than one object with the same PK)   *
*                                                           *
************************************************************/
int NH_check(nic_handle_t *nh_ptr, SQ_connection_t *sql_connection)
{
range_t range;
long range_id;
long nic_id=nh_ptr->nic_id;


  range.space=nh_ptr->space;
  if(nh_ptr->source)range.source=nh_ptr->source; else range.source="";
  
  if (nic_id == AUTO_NIC_ID) {
  /* NIC handle is an AUTO one */
  /* get first range (with min range_end) for a given space */
   range_id = get_min_range(&range, sql_connection); 
   if(range_id<0)  return(-1); /* in case of an error */

   if ( range_id==0 ) {
  /* Nothing found */
  /* Allocate a hic-hdl in a new space with the first range {0-1} in it*/
 	nic_id=1;
   } else {
      if ( range.end == MAX_NIC_ID ) return(0); /* space is fully occupied  */
      /* attach to range and may be join with next */
       nic_id = range.end+1;
   }
  }
/* if not AUTO */  
  else {
    range_id = get_range(nic_id, &range, sql_connection);
    if(range_id <0)  return(-1); /* in case of an error */
    if(range_id!=0)  return(0); /* this nic_id already exists */
  }
  nh_ptr->nic_id=nic_id;
 return(1); 
}

/************************************************************
* long NH_free()                                             *
*                                                           *
* Delete a NIC handle from the repository                   *
*                                                           *
* To finalize changes make commit/rollback                  *
*                                                           *
* Returns:                                                  *
*  1 - success                                              *
*  0 - error (range is not founnd)                          *
* -1 - error (f.e. more than one object with the same PK)   *
*                                                           *
************************************************************/
int NH_free(nic_handle_t *nh_ptr, SQ_connection_t *sql_connection)
{
range_t range;
long range_id;
int old_start;
long nic_id=nh_ptr->nic_id;


  range.space=nh_ptr->space;
  if(nh_ptr->source)range.source=nh_ptr->source; else range.source="";
  
  /* Search for the range containing the nic-handle */
  range_id = get_range(nic_id, &range, sql_connection);
  /* If range is not found or an error occcured - return */
  if(range_id==0) { return(0); }
  if(range_id<0)  { return(-1); }
  
  if(nic_id == range.start) {
  /* update range start and may be detele range and space */
   range.start+=1;
   range_id=update_range(range_id, &range, sql_connection);
   if(range_id<=0) {  return(-1); }
  }
  else if(nic_id == range.end) { 
  /* update range end and may be detele range and space */
         range.end-=1;
         range_id=update_range(range_id, &range, sql_connection);
	 if(range_id<=0) {  return(-1); }
  }
  else { 
       /* split the range into two */ 
       /* shrink the old one */
         old_start=range.start;
         range.start=nic_id+1;
         range_id=update_range(range_id, &range, sql_connection);
	 if(range_id<=0) { return(-1); }
       /* create a new one */
         range.start=old_start;
         range.end=nic_id-1;
         range_id=create_range(&range, sql_connection);
	 if(range_id<=0) {  return(-1); }
  }
  
  return(1);
}


/************************************************************
* int NH_register()                                         *
*                                                           *
* Get a NIC handle from the repository                      *
*                                                           *
*                                                           *
* Returns:                                                  *
* 1 - success                                               *
* 0  - nic_id already exists or space is fully occupied     *
* -1 - error (f.e. more than one object with the same PK)   *
*                                                           *
************************************************************/
int NH_register(nic_handle_t *nh_ptr, SQ_connection_t *sql_connection)
{
range_t range;
long range_id;
long nic_id=nh_ptr->nic_id;




 /* Yiu should check for nh first for AUTO nic-handles */
  if (nic_id == AUTO_NIC_ID) { return(0); };

  range.space=nh_ptr->space;
  if(nh_ptr->source)range.source=nh_ptr->source; else range.source="";

  range_id = get_range(nic_id, &range, sql_connection);
  if(range_id <0)  { return(-1); } /* in case of an error */
  if(range_id!=0)  { return(0); } /* this nic_id already exists */
 
  /* check if we can attach to existing next range */
  range_id = get_range(nic_id+1, &range, sql_connection);
  if(range_id <0)  { return(-1); } /* in case of an error */

    if( range_id>0 ) { 
    /* attach to range and may be join with previous */ 
     range.start-=1;
     range_id=update_range(range_id, &range, sql_connection);
     if(range_id<=0) { return(-1); }
    }
    else {
     /* check if we can attach to existing previous range */
      if(nic_id>0) range_id = get_range(nic_id-1, &range, sql_connection);
      else range_id=0; /* there is no previous range in this case (nic_id==0) */
      if(range_id <0)  { return(-1); } /* in case of an error */
      if( range_id>0 ) { 
      /* attach to range and may be join with next */
       range.end+=1;
       range_id=update_range(range_id, &range, sql_connection);
       if(range_id<=0) { return(-1); }
      }
      else {
       /* If we cannot attach to any existing range - create new {nic_id-nic_id} */
       range.end=range.start=nic_id;
       range_id=create_range(&range, sql_connection);
       if(range_id <=0)  { return(-1); } /* in case of an error */
      }
    }  
 return(1);
}

/*
 Free nic_handle_t structure 
 */
void free_nh(nic_handle_t *nh_ptr)
{
 if(nh_ptr){
   if(nh_ptr->space)free(nh_ptr->space);
   if(nh_ptr->source)free(nh_ptr->source);
   free(nh_ptr);
 }
}


/************************************************************
* long get_range()                                          *
*                                                           *
* Searches for the range of the space containing            *
* the specified nic_id                                      *
*                                                           *
* To request to search for the firt (min) range, nic_id     *
* should be set to -1.                                       *
*                                                           *
* Returns:                                                  *
* >0 - range exists, returns range_id                       *
* 0  - range does not exist                                 *
* -1 - DB error (f.e. more than one object with the same PK)*
*                                                           *
* **********************************************************/
static long get_range(long nic_id, range_t *prange, SQ_connection_t *sql_connection)
{
SQ_result_set_t *sql_result;
SQ_row_t *sql_row;
char *sql_str;
GString *query;
long range_id=0;
int sql_err;

 if ((query = g_string_sized_new(STR_L)) == NULL){ 
  fprintf(stderr, "E: cannot allocate gstring\n"); 
  return(-1); 
 }
 
/* Define row numbers in the result of the query */
#define RANGE_ID 0
#define RANGE_START 1
#define RANGE_END 2
 
 if (nic_id<0) {
  /* requesting the first (min) range */
  g_string_sprintf(query, "SELECT range_id, range_start, range_end " 
                          "FROM nic_hdl "
                          "WHERE space='%s' "
			  "AND source='%s' "
                          "AND (range_start=0 "
                          "OR  range_start=1) ",
			  prange->space, prange->source);
 } else {

  g_string_sprintf(query, "SELECT range_id, range_start, range_end " 
                          "FROM nic_hdl "
                          "WHERE space='%s' "
			  "AND source='%s' "
                          "AND range_start<=%ld "
                          "AND range_end>=%ld ",
			  prange->space, prange->source, nic_id, nic_id);
 }
        
/* execute query */
/* fprintf(stderr, "get_range[%s]\n", query->str); */
 sql_err=SQ_execute_query(sql_connection, query->str, &sql_result);
 g_string_free(query, TRUE);
 
 if(sql_err) {
   fprintf(stderr,"ERROR: %s\n", SQ_error(sql_connection));
   return(-1);
 }

 if ((sql_row = SQ_row_next(sql_result)) != NULL) {
/* Object exists */
   sql_str = SQ_get_column_string(sql_result, sql_row, RANGE_ID);
   if (sql_str != NULL) {
     range_id = atol(sql_str);
     free(sql_str);
   }
   sql_str = SQ_get_column_string(sql_result, sql_row, RANGE_START);
   if (sql_str != NULL) {
     prange->start = atoi(sql_str);
     free(sql_str);
   }
   sql_str = SQ_get_column_string(sql_result, sql_row, RANGE_END);
   if (sql_str != NULL) {
     prange->end = atoi(sql_str);
     free(sql_str);
   }

/* We must process all the rows of the result */
/* otherwise we'll have them as part of the next qry */      
   while ( (sql_row = SQ_row_next(sql_result)) != NULL) range_id=-1;
 } else 
      range_id=0;  // object does not exist
   
 if(sql_result)SQ_free_result(sql_result);
 return(range_id);
}




/************************************************************
* long update_range()                                       *
*                                                           *
* Updates the range by changing the boundaries              *
* Deletes the range if nothing left                         *
* Merges with neighbor ranges if there is no gap between    *
*                                                           *
* We never update range. We create a new one with specified * 
* limits and mark old one(s) for deletion, so that we can   *
* make commit/rollback properly. This is possible as the    * 
* primary keys are (range_id, range_start, range_end)       *
*                                                           *
* To finalize changes make commit/rollback                  *
*                                                           *
* Returns:                                                  *
* >0 - returns range_id on success                          *
* -1 - error (f.e. more than one object with the same PK)   *
*                                                           *
************************************************************/
              
static long update_range(long range_id, range_t *p_newrange, SQ_connection_t *sql_connection)
{
GString *query;
range_t range;
long prev_range_id, next_range_id;
int num;
int sql_err;

/* Allocate memory */
 if ((query = g_string_sized_new(STR_L)) == NULL){ 
  fprintf(stderr, "E: cannot allocate gstring\n"); 
  return(-1); 
 }

/* Do range check */
 if (( p_newrange->end > MAX_RANGE ) || ( p_newrange->start < MIN_RANGE )) return(-1);

/* Check if the range collapses */
 if ( p_newrange->end < p_newrange->start ) {
 /* then delete the range */  
 /* Do this by marking the range for deletion for further commit/rollback */
   g_string_sprintf(query, "DELETE FROM nic_hdl "
                           "WHERE range_id=%ld ",
                           range_id);
   
/*   fprintf(stderr, "update_range[%s]\n", query->str); */
   sql_err=SQ_execute_query(sql_connection, query->str, (SQ_result_set_t **)NULL);
   if(sql_err) {
    /* An error occured */
    g_string_free(query, TRUE);
    return(-1);
   }
   num = mysql_affected_rows(sql_connection); 
   /* this should not happen */
   if(num==0) die;
 
 }
 else {
  /* update the range for the same space/source */
  range.space=p_newrange->space;
  range.source=p_newrange->source; 
  /* Check if we can join with previous range of the same space */
  prev_range_id=get_range(p_newrange->start-1, &range, sql_connection);
  /* Check if such range exists and it is not ours (this happens when we are shrinking */
  if((prev_range_id>0) && (prev_range_id!=range_id)) {
   /* acquire the previous range */
   /* mark it for deletion for commit/rollback */
   g_string_sprintf(query, "DELETE FROM nic_hdl "
                           "WHERE range_id=%ld ",
                           prev_range_id);

/*    fprintf(stderr, "update_range[%s]\n", query->str); */
   sql_err=SQ_execute_query(sql_connection, query->str, (SQ_result_set_t **)NULL);
   if(sql_err) {
    /* An error occured */
    g_string_free(query, TRUE);
    return(-1);
   }
   num = mysql_affected_rows(sql_connection); 
   /* this should not happen */
   if(num==0) die;
   
   /* expand the boundaries */
   p_newrange->start=range.start;
  }

/* Check if we can join with next range of the same space */
  next_range_id=get_range(p_newrange->end+1, &range, sql_connection);
  /* Check if such range exists and it is not ours (this happens when we are shrinking) */
  if((next_range_id>0) && (next_range_id!=range_id)) {
   /* acquire the next range */
   /* mark it for deletion for commit/rollback */
   g_string_sprintf(query, "DELETE FROM nic_hdl "
                           "WHERE range_id=%ld ",
                           next_range_id);

/*   fprintf(stderr, "update_range[%s]\n", query->str); */
   sql_err=SQ_execute_query(sql_connection, query->str, (SQ_result_set_t **)NULL);
   if(sql_err) {
    /* An error occured */
    g_string_free(query, TRUE);
    return(-1);
   }
   num = mysql_affected_rows(sql_connection); 
   /* this should not happen */
   if(num==0) die;
   
   /* expand the boundaries */
   p_newrange->end=range.end;
  }
 
/* Now make a larger range. Mark it for commit/rollback */ 
   g_string_sprintf(query, "UPDATE nic_hdl "
                           "SET range_start=%ld, range_end=%ld "
			   "WHERE range_id=%ld",
                           p_newrange->start, p_newrange->end, range_id);

/*   fprintf(stderr, "update_range[%s]\n", query->str); */
   sql_err=SQ_execute_query(sql_connection, query->str, (SQ_result_set_t **)NULL);
   if(sql_err) {
    /* An error occured */
    g_string_free(query, TRUE);
    return(-1);
   }
   num = mysql_affected_rows(sql_connection); 
   /* this should not happen */
   if(num==0) die;
 } /* update the range */

 g_string_free(query, TRUE);
 return (range_id);
}
               
/************************************************************
* long create_range()                                       *
*                                                           *
* Creates a new range in a given name space                 *
*                                                           *
* To finalize changes make commit/rollback                  *
*                                                           *
* Returns:                                                  *
* >0 - returns range_id on success                          *
* -1 - error (f.e. more than one object with the same PK)   *
*                                                           *
************************************************************/
                
static long create_range(range_t *p_range, SQ_connection_t *sql_connection)
{
GString *query;
int sql_err, num;

 /* Allocate memory */
 if ((query = g_string_sized_new(STR_L)) == NULL){ 
  fprintf(stderr, "E: cannot allocate gstring\n"); 
  return(-1); 
 }
 
 
 g_string_sprintf(query, "INSERT nic_hdl "
                         "SET space='%s', source='%s', range_start=%ld, range_end=%ld ",
                          p_range->space, p_range->source, p_range->start, p_range->end);

/* fprintf(stderr, "create_range[%s]\n", query->str); */
 sql_err=SQ_execute_query(sql_connection, query->str, (SQ_result_set_t **)NULL);
 g_string_free(query, TRUE);
  
   if(sql_err) {
    /* An error occured */
    return(-1);
   }
   num = mysql_affected_rows(sql_connection); 
   /* this should not happen */
   if(num==0) die;
 return(mysql_insert_id(sql_connection));
}

