/***************************************
  $Revision: 1.6 $

  Status: NOT REVUED, NOT TESTED

 Author(s):       Andrei Robachevsky

  ******************/ /******************
  Modification History:
        andrei (10/04/2000) Created.
  ******************/ /******************
  Copyright (c) 2000                              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 <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

/*
CREATE TABLE nic_hdl (
  thread_id(11) DEFAULT '0' NOT NULL,
  range_id int(10) unsigned DEFAULT '0' NOT NULL auto_increment, 
  range_start int(10) DEFAULT '0' NOT NULL,
  range_end int(10) DEFAULT '0' NOT NULL,
  space char(4) DEFAULT '' NOT NULL,
  source char(10) DEFAULT '' NOT NULL,
  PRIMARY KEY (range_id, range_start, range_end)
);

*/

#define get_min_range(prange, sql_connection) get_range(MIN_NIC_ID, 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, int commit_now);
static long create_range(range_t *p_range, SQ_connection_t *sql_connection, int commit_now);

/************************************************************
* 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    *
*                                                           *
* Acceptable format is:                                     *
* [A-Z][A-Z]*[1-9][0-9]*(-[A-Z][A-Z]*)?                     *
*                                                           *
* 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, int commit_now)
{
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, commit_now);
   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, commit_now);
	 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, commit_now);
	 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, commit_now);
	 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, int commit_now)
{
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, commit_now);
     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, commit_now);
       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, commit_now);
       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 MIN_NIC_ID                               *
*                                                           *
* 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==MIN_NIC_ID) {
  /* 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, int commit_now)
{
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 */
  if(commit_now)
   g_string_sprintf(query, "DELETE FROM nic_hdl " 
                           "WHERE range_id=%ld ",
                            range_id);
  else  
   g_string_sprintf(query, "UPDATE nic_hdl SET thread_id=%d "
                           "WHERE range_id=%ld ",
			    NH_DELETE, 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 */
   if(commit_now)
      g_string_sprintf(query, "DELETE FROM nic_hdl "
                              "WHERE range_id=%ld ",
                               prev_range_id);
   else
      g_string_sprintf(query, "UPDATE nic_hdl SET thread_id=%d "
                              "WHERE range_id=%ld ",
			       NH_DELETE, 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 */
   if(commit_now)
     g_string_sprintf(query, "DELETE FROM nic_hdl "
                             "WHERE range_id=%ld ",
                              next_range_id);
   else	   
     g_string_sprintf(query, "UPDATE nic_hdl SET thread_id=%d "
                             "WHERE range_id=%ld ",
			      NH_DELETE, 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 current for deletion and new for commit/rollback */ 
  if(commit_now)
   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);
  else {

   g_string_sprintf(query, "UPDATE nic_hdl SET thread_id=%d "
                           "WHERE range_id=%ld ",
			   NH_DELETE, 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;
   
   g_string_sprintf(query, "INSERT nic_hdl "
                           "SET thread_id=%d, range_id=%ld, space='%s', source='%s', range_start=%ld, range_end=%ld ",
                           NH_INSERT, range_id, p_newrange->space, p_newrange->source, p_newrange->start, p_newrange->end);  
  } 

/*   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, int commit_now)
{
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); 
 }
 
 if(commit_now)
  g_string_sprintf(query, "INSERT nic_hdl "
                          "SET thread_id=0, space='%s', source='%s', range_start=%ld, range_end=%ld ",
                           p_range->space, p_range->source, p_range->start, p_range->end);
 else	 
  g_string_sprintf(query, "INSERT nic_hdl "
                          "SET thread_id=%d, space='%s', source='%s', range_start=%ld, range_end=%ld ",
                           NH_INSERT, 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));
}


/************************************************************
* int NH_comrol()                                           *
*                                                           *
* Commits or rolls back changes to NHR                      *
*                                                           *
*                                                           *
* Returns:                                                  *
* >0 - success                                              *
* -1 - SQL error                                            *
*                                                           *
************************************************************/
 
int NH_comrol(SQ_connection_t *sql_connection, int thread_ins, int thread_del)
{
GString *query;
int sql_err;

 /* Allocate memory */
 if ((query = g_string_sized_new(STR_L)) == NULL){ 
  fprintf(stderr, "E: cannot allocate gstring\n"); 
  return(-1); 
 }
 
  g_string_sprintf(query, "DELETE FROM nic_hdl "
			  "WHERE thread_id=%d ",
                          thread_del);

/* fprintf(stderr, "create_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);
    fprintf(stderr,"ERROR: %s\n", SQ_error(sql_connection));
    die;
 }

 g_string_sprintf(query, "UPDATE nic_hdl "
                         "SET thread_id=0 "
			 "WHERE thread_id=%d ",
                          thread_ins);

/* 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 */
   fprintf(stderr,"ERROR: %s\n", SQ_error(sql_connection));
   die;
 }
 
 return(1);
		
}


