#define _GNU_SOURCE
#include <libconfig.h>

#include <string.h>
#include <stdlib.h>

#include <arpa/inet.h>

#include "config_libconfig.h"
#include "config_libconfig_private.h"
#include "../config_userapi.h"
#include "errno.h"

#if ( LIBCONFIG_VER_MAJOR == 1 && LIBCONFIG_VER_MINOR < 5)
#define config_setting_lookup config_lookup_from
#endif

void config_libconfig_end(void );

int read_strlist(paramdef_t *cfgoptions,config_setting_t *setting, char *cfgpath)
{
const char *str;
int st;

   cfgoptions->numelt=config_setting_length(setting);
   cfgoptions->strlistptr=malloc(sizeof(char *) * (cfgoptions->numelt));
   st=0;
   for (int i=0; i< cfgoptions->numelt && cfgoptions->strlistptr != NULL; i++) {
       str=config_setting_get_string_elem(setting,i);
       if (str==NULL) {
          printf("[LIBCONFIG] %s%i  not found in config file\n", cfgoptions->optname,i);
       } else {
            cfgoptions->strlistptr[i]=malloc(strlen(str)+1);
            sprintf(cfgoptions->strlistptr[i],"%s",str);
	    st++;
            printf_params("[LIBCONFIG] %s%i: %s\n", cfgpath,i,cfgoptions->strlistptr[i]);
       }
   }
   return st;
}

int read_intarray(paramdef_t *cfgoptions,config_setting_t *setting, char *cfgpath)
{

   cfgoptions->numelt=config_setting_length(setting);
   
   if (cfgoptions->numelt > 0) {
       cfgoptions->iptr=malloc(sizeof(int) * (cfgoptions->numelt));
       for (int i=0; i< cfgoptions->numelt && cfgoptions->iptr != NULL; i++) {
            cfgoptions->iptr[i]=config_setting_get_int_elem(setting,i);
            printf_params("[LIBCONFIG] %s[%i]: %i\n", cfgpath,i,cfgoptions->iptr[i]);
       } /* for loop on array element... */
   }
   return cfgoptions->numelt;
}




int config_libconfig_get(paramdef_t *cfgoptions,int numoptions, char *prefix )
{


  config_setting_t *setting;
  char *str;
  int i,u;
  long long int llu;
  double dbl; 
  int rst;
  int status=0;
  int notfound;
  int defval;
  int fatalerror=0;
  char *cfgpath; /* listname.[listindex].paramname */
  int numdefvals=0;
  
  i = (prefix ==NULL) ? 0 : strlen(prefix); 
  cfgpath = malloc( i+ MAX_OPTNAME_SIZE +1);
  if (cfgpath == NULL) {
     fprintf(stderr,"[LIBCONFIG] %s %i malloc error,  %s\n", __FILE__, __LINE__,strerror(errno));
     return -1;
  }
  for(i=0;i<numoptions;i++) {

     if (prefix != NULL) {
         sprintf(cfgpath,"%s.%s",prefix,cfgoptions[i].optname);
     } else {
         sprintf(cfgpath,"%s",cfgoptions[i].optname);
     }

     if( (cfgoptions->paramflags & PARAMFLAG_DONOTREAD) != 0) {
         printf_params("[LIBCONFIG] %s.%s ignored\n", cfgpath,cfgoptions[i].optname );
         continue;
     }
     notfound=0;
     defval=0;
     switch(cfgoptions[i].type)
       {
       	case TYPE_STRING:
           
           if ( config_lookup_string(&(libconfig_privdata.cfg),cfgpath, (const char **)&str)) {
              if ( cfgoptions[i].numelt > 0  && str != NULL && strlen(str) >= cfgoptions[i].numelt ) {
                  fprintf(stderr,"[LIBCONFIG] %s:  %s exceeds maximum length of %i bytes, value truncated\n",
                           cfgpath,str,cfgoptions[i].numelt); 
                  str[strlen(str)-1] = 0;
              }
              if (cfgoptions[i].numelt == 0 ) {
                  config_check_valptr(&(cfgoptions[i]), (char **)(&(cfgoptions[i].strptr)), sizeof(char *));
                  config_check_valptr(&(cfgoptions[i]), cfgoptions[i].strptr, strlen(str)+1);
                  sprintf( *(cfgoptions[i].strptr) , "%s", str);
                  printf_params("[LIBCONFIG] %s: %s\n", cfgpath,*(cfgoptions[i].strptr) );
              } else {
                 sprintf( (char *)(cfgoptions[i].strptr) , "%s", str);
                 printf_params("[LIBCONFIG] %s: %s\n", cfgpath,(char *)cfgoptions[i].strptr );
              }
           } else {
	      if( cfgoptions[i].defstrval != NULL) {
                 defval=1;
 
                 if (cfgoptions[i].numelt == 0 ) {
                     config_check_valptr(&(cfgoptions[i]), (char **)(&(cfgoptions[i].strptr)), sizeof(char *));
                     config_check_valptr(&(cfgoptions[i]), cfgoptions[i].strptr, strlen(cfgoptions[i].defstrval)+1);
                     sprintf(*(cfgoptions[i].strptr), "%s",cfgoptions[i].defstrval);
                     printf_params("[LIBCONFIG] %s set to default value %s\n", cfgpath, *(cfgoptions[i].strptr));
                 } else {
                    sprintf((char *)(cfgoptions[i].strptr), "%s",cfgoptions[i].defstrval);
                    printf_params("[LIBCONFIG] %s set to default value %s\n", cfgpath, (char *)cfgoptions[i].strptr);
                 }
              } else {
	         notfound=1;
              } 
	   }
       break;
       	case TYPE_STRINGLIST:
	   setting = config_setting_lookup (config_root_setting(&(libconfig_privdata.cfg)),cfgpath );
           if ( setting != NULL) {
              read_strlist(&cfgoptions[i],setting,cfgpath);
           } else {
              if( cfgoptions[i].defstrlistval != NULL) {
                  cfgoptions[i].strlistptr=cfgoptions[i].defstrlistval;
                  defval=1;
		for(int j=0; j<cfgoptions[i].numelt; j++)
                     printf_params("[LIBCONFIG] %s%i set to default value %s\n", cfgpath,j, cfgoptions[i].strlistptr[j]);
              } else {
                notfound=1;
              }
	   }
       break;
       	case TYPE_UINT8:
       	case TYPE_INT8:	
       	case TYPE_UINT16:
       	case TYPE_INT16:
       	case TYPE_UINT32:
       	case TYPE_INT32:
       	case TYPE_MASK:	
           config_check_valptr(&(cfgoptions[i]), (char **)(&(cfgoptions[i].iptr)),sizeof(int32_t));
           if ( config_lookup_int(&(libconfig_privdata.cfg),cfgpath, &u)) {
	      config_assign_int(&(cfgoptions[i]),cfgpath,u);
           } else {
	      if( ((cfgoptions[i].paramflags & PARAMFLAG_MANDATORY) == 0)) {
                 config_assign_int(&(cfgoptions[i]),cfgpath,cfgoptions[i].defintval);
                 defval=1;
                 printf_params("[LIBCONFIG] %s set to default value\n", cfgpath);
              } else {
	         notfound=1;
              }
	   }	      
        break;
       	case TYPE_UINT64:
       	case TYPE_INT64:
           config_check_valptr(&(cfgoptions[i]), (char **)&(cfgoptions[i].i64ptr),sizeof(long long));
           if ( config_lookup_int64(&(libconfig_privdata.cfg),cfgpath, &llu)) {
              if(cfgoptions[i].type==TYPE_UINT64) {
                 *(cfgoptions[i].u64ptr) = (uint64_t)llu;
                 printf_params("[LIBCONFIG] %s: %llu\n", cfgpath,(long long unsigned)(*(cfgoptions[i].u64ptr)) );
              } else {
                 *(cfgoptions[i].iptr) = llu;
                 printf_params("[LIBCONFIG] %s: %lli\n", cfgpath,(long long unsigned)(*(cfgoptions[i].i64ptr)) ); 
              }
           } else {
	      if( ((cfgoptions[i].paramflags & PARAMFLAG_MANDATORY) == 0)) {
                 *(cfgoptions[i].u64ptr)=cfgoptions[i].defuintval;
                 defval=1;
                 printf_params("[LIBCONFIG] %s set to default value %llu\n", cfgpath, (long long unsigned)(*(cfgoptions[i].u64ptr)));
              } else {
	         notfound=1;
              }
	   }	      
        break;        
       	case TYPE_UINTARRAY:
       	case TYPE_INTARRAY:
	   setting = config_setting_lookup (config_root_setting(&(libconfig_privdata.cfg)),cfgpath );
           if ( setting != NULL) {
              read_intarray(&cfgoptions[i],setting,cfgpath);
           } else {
              if( cfgoptions[i].defintarrayval != NULL) {
                config_check_valptr(&(cfgoptions[i]),(char **)&(cfgoptions[i].iptr), sizeof(int32_t*));
                cfgoptions[i].iptr=cfgoptions[i].defintarrayval;
                defval=1;
                for (int j=0; j<cfgoptions[i].numelt ; j++) {
                    printf_params("[LIBCONFIG] %s[%i] set to default value %i\n", cfgpath,j,(int)cfgoptions[i].iptr[j]);
                }
              } else {
                notfound=1;
              }
	   }    
        break;
       	case TYPE_DOUBLE:
           config_check_valptr(&(cfgoptions[i]), (char **)&(cfgoptions[i].dblptr),sizeof(double));
           if ( config_lookup_float(&(libconfig_privdata.cfg),cfgpath, &dbl)) {
                 *(cfgoptions[i].dblptr) = dbl;
                 printf_params("[LIBCONFIG] %s: %lf\n", cfgpath,*(cfgoptions[i].dblptr) );
           } else {
	      if( ((cfgoptions[i].paramflags & PARAMFLAG_MANDATORY) == 0)) {
                 *(cfgoptions[i].u64ptr)=cfgoptions[i].defdblval;
                 defval=1;
                 printf_params("[LIBCONFIG] %s set to default value %lf\n", cfgpath, *(cfgoptions[i].dblptr));
              } else {
	         notfound=1;
              }
	   }	      
        break;  
       	case TYPE_IPV4ADDR:
           config_check_valptr(&(cfgoptions[i]),(char **)&(cfgoptions[i].uptr), sizeof(int));
           if ( !config_lookup_string(&(libconfig_privdata.cfg),cfgpath, (const char **)&str)) {
	      str=cfgoptions[i].defstrval;
              defval=1;
	      printf_params("[LIBCONFIG] %s set to default value %s\n", cfgpath, str);
	   }
	   if (str != NULL) {
              rst=inet_pton(AF_INET, str,cfgoptions[i].uptr );
	      if (rst == 1 && *(cfgoptions[i].uptr) > 0) {
                 printf_params("[LIBCONFIG] %s: %s\n", cfgpath,str );
              } else {
		 if ( strncmp(str,ANY_IPV4ADDR_STRING,sizeof(ANY_IPV4ADDR_STRING)) == 0) {
		    printf_params("[LIBCONFIG] %s:%s (INADDR_ANY) \n",cfgpath,str);
		    *cfgoptions[i].uptr=INADDR_ANY;
		 } else {
		    fprintf(stderr,"[LIBCONFIG] %s not valid for %s \n", str, cfgpath);
                    fatalerror=1;
                 }
              }
           } else {
	      notfound=1;
	   }
              
        break;
       	case TYPE_LIST:
	   setting = config_setting_lookup (config_root_setting(&(libconfig_privdata.cfg)),cfgpath );
           if ( setting) {
              cfgoptions[i].numelt=config_setting_length(setting);
           } else {
              notfound=1;
	   }
       break;
       default:
            fprintf(stderr,"[LIBCONFIG] %s type %i  not supported\n", cfgpath,cfgoptions[i].type);
            fatalerror=1;
       break;
       } /* switch on param type */
    if( notfound == 1) {
        printf("[LIBCONFIG] %s not found in %s ", cfgpath,libconfig_privdata.configfile );
        if ( (cfgoptions[i].paramflags & PARAMFLAG_MANDATORY) != 0) {
            fatalerror=1;
            printf("  mandatory parameter missing\n");
        } else {
           printf("\n");
        }
    } else {
      if (defval == 1) {
          numdefvals++;
	  cfgoptions[i].paramflags = cfgoptions[i].paramflags |  PARAMFLAG_PARAMSETDEF;
      } else {
          cfgoptions[i].paramflags = cfgoptions[i].paramflags |  PARAMFLAG_PARAMSET;
      }
      status++;
    }
  } /* for loop on options */
  printf("[LIBCONFIG] %s: %i/%i parameters successfully set, (%i to default value)\n",
         ((prefix == NULL)?"(root)":prefix), 
         status,numoptions,numdefvals );
  if (fatalerror == 1) {
      fprintf(stderr,"[LIBCONFIG] fatal errors found when processing  %s \n", libconfig_privdata.configfile );
      config_libconfig_end();
      end_configmodule();
  }
  free(cfgpath);
  return status;
}

int config_libconfig_getlist(paramlist_def_t *ParamList, 
                                   paramdef_t *params, int numparams, char *prefix)
{
config_setting_t *setting;
int i,j,status;
char *listpath=NULL;
char cfgpath[MAX_OPTNAME_SIZE*2 + 6]; /* prefix.listname.[listindex] */

    if (prefix != NULL)
        {
        i=asprintf(&listpath ,"%s.%s",prefix,ParamList->listname);
        }
    else
        {
        i=asprintf(&listpath ,"%s",ParamList->listname);
        }
    setting = config_lookup(&(libconfig_privdata.cfg), listpath);
    if ( setting) {
        status = ParamList->numelt = config_setting_length(setting);
        printf_params("[LIBCONFIG] %i %s in config file %s \n", 
               ParamList->numelt,listpath,libconfig_privdata.configfile );
    } else {
        printf("[LIBCONFIG] list %s not found in config file %s \n", 
               listpath,libconfig_privdata.configfile );
        ParamList->numelt= 0;
        status = -1;
    }
    if (ParamList->numelt > 0 && params != NULL) {
        ParamList->paramarray = malloc(ParamList->numelt * sizeof(paramdef_t *));
        if ( ParamList->paramarray != NULL) {
             config_get_if()->ptrs[config_get_if()->numptrs] = (char *)(ParamList->paramarray); 
             config_get_if()->numptrs++;
        } else {
             fprintf (stderr,"[LIBCONFIG] %s %d malloc error\n",__FILE__, __LINE__);
             exit(-1);
        }
        for (i=0 ; i < ParamList->numelt ; i++) {
            ParamList->paramarray[i] = malloc(numparams * sizeof(paramdef_t));
            if ( ParamList->paramarray[i] != NULL) {
                 config_get_if()->ptrs[config_get_if()->numptrs] = (char *)(ParamList->paramarray[i]); 
                 config_get_if()->numptrs++;
            } else {
                 fprintf (stderr,"[LIBCONFIG] %s %d malloc error\n",__FILE__, __LINE__);
                 exit(-1);
            }
            
            memcpy(ParamList->paramarray[i], params, sizeof(paramdef_t)*numparams);
            for (j=0;j<numparams;j++) {
                ParamList->paramarray[i][j].strptr = NULL ;
                } 
            sprintf(cfgpath,"%s.[%i]",listpath,i);
            config_libconfig_get(ParamList->paramarray[i], numparams, cfgpath );
        } /* for i... */
    } /* ParamList->numelt > 0 && params != NULL */
    if (listpath != NULL)
        free(listpath);
    return status;
}

int config_libconfig_init(char *cfgP[], int numP)
{
  config_init(&(libconfig_privdata.cfg));
  libconfig_privdata.configfile = strdup((char *)cfgP[0]);
  config_get_if()->numptrs=0;
  memset(config_get_if()->ptrs,0,sizeof(void *) * CONFIG_MAX_ALLOCATEDPTRS);

  /* Read the file. If there is an error, report it and exit. */
  if(! config_read_file(&(libconfig_privdata.cfg), libconfig_privdata.configfile)) {
    fprintf(stderr,"[LIBCONFIG] %s %d file %s - %d - %s\n",__FILE__, __LINE__,
                   libconfig_privdata.configfile, config_error_line(&(libconfig_privdata.cfg)),
                   config_error_text(&(libconfig_privdata.cfg)));
    config_destroy(&(libconfig_privdata.cfg));
    printf( "\n");
    return -1;
  }
  

  return 0;
}


void config_libconfig_end(void )
{
  config_destroy(&(libconfig_privdata.cfg));
  if ( libconfig_privdata.configfile != NULL ) {
     free(libconfig_privdata.configfile);
  } 
  
}