/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*! \file common/config/config_load_configmodule.c
 * \brief configuration module, load the shared library implementing the configuration module 
 * \author Francois TABURET
 * \date 2017
 * \version 0.1
 * \company NOKIA BellLabs France
 * \email: francois.taburet@nokia-bell-labs.com
 * \note
 * \warning
 */
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <dlfcn.h>

#define CONFIG_LOADCONFIG_MAIN
#include "config_load_configmodule.h"
#include "config_userapi.h"
#define CONFIG_SHAREDLIBFORMAT "libparams_%s.so"

int load_config_sharedlib(configmodule_interface_t *cfgptr)
{
void *lib_handle;
char fname[128];
char libname[FILENAME_MAX]; 
int st;

   st=0;  
   sprintf(libname,CONFIG_SHAREDLIBFORMAT,cfgptr->cfgmode);

   lib_handle = dlopen(libname,RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE);
   if (!lib_handle) {
      fprintf(stderr,"[CONFIG] %s %d Error calling dlopen(%s): %s\n",__FILE__, __LINE__, libname,dlerror());
      st = -1;
   } else { 
      sprintf (fname,"config_%s_init",cfgptr->cfgmode);
      cfgptr->init = dlsym(lib_handle,fname);

      if (cfgptr->init == NULL ) {
         printf("[CONFIG] %s %d no function %s for config mode %s\n",
               __FILE__, __LINE__,fname, cfgptr->cfgmode);
      } else {
         st=cfgptr->init(cfgptr->cfgP,cfgptr->num_cfgP); 
         printf("[CONFIG] function %s returned %i\n",
               fname, st);	 
      }

      sprintf (fname,"config_%s_get",cfgptr->cfgmode);
      cfgptr->get = dlsym(lib_handle,fname);
      if (cfgptr->get == NULL ) { 
         printf("[CONFIG] %s %d no function %s for config mode %s\n",
               __FILE__, __LINE__,fname, cfgptr->cfgmode);
	 st = -1;
      }
      
      sprintf (fname,"config_%s_getlist",cfgptr->cfgmode);
      cfgptr->getlist = dlsym(lib_handle,fname);
      if (cfgptr->getlist == NULL ) { 
         printf("[CONFIG] %s %d no function %s for config mode %s\n",
               __FILE__, __LINE__,fname, cfgptr->cfgmode);
	 st = -1;
      }

      sprintf (fname,"config_%s_end",cfgptr->cfgmode);
      cfgptr->end = dlsym(lib_handle,fname);
      if (cfgptr->getlist == NULL ) { 
         printf("[CONFIG] %s %d no function %s for config mode %s\n",
               __FILE__, __LINE__,fname, cfgptr->cfgmode);
      }      
   } 
   
   return st;	       
}
/*-----------------------------------------------------------------------------------*/
/* from here: interface implementtion of the configuration module */
int nooptfunc(void) {
   return 0;
};

int config_cmdlineonly_getlist(paramlist_def_t *ParamList, 
                                   paramdef_t *params, int numparams, char *prefix)
{
     ParamList->numelt = 0;
     return 0;
}


int config_cmdlineonly_get(paramdef_t *cfgoptions,int numoptions, char *prefix )
{
  int defval;
  int fatalerror=0;
  int numdefvals=0;
  

  for(int i=0;i<numoptions;i++) {
     defval=0;
     switch(cfgoptions[i].type) {
       case TYPE_STRING:
            defval=config_setdefault_string(&(cfgoptions[i]), prefix);
       break;
       case TYPE_STRINGLIST:
            defval=config_setdefault_stringlist(&(cfgoptions[i]), prefix);
       break;
       	case TYPE_UINT8:
       	case TYPE_INT8:	
       	case TYPE_UINT16:
       	case TYPE_INT16:
       	case TYPE_UINT32:
       	case TYPE_INT32:
       	case TYPE_MASK:	
             defval=config_setdefault_int(&(cfgoptions[i]), prefix);
        break;
       	case TYPE_UINT64:
       	case TYPE_INT64:
             defval=config_setdefault_int64(&(cfgoptions[i]), prefix);
        break;        
       	case TYPE_UINTARRAY:
       	case TYPE_INTARRAY:
             defval=config_setdefault_intlist(&(cfgoptions[i]), prefix);
        break;
       	case TYPE_DOUBLE:
             defval=config_setdefault_double(&(cfgoptions[i]), prefix);  
        break;  
       	case TYPE_IPV4ADDR:
             defval=config_setdefault_ipv4addr(&(cfgoptions[i]), prefix); 
              
        break;
        default:
            fprintf(stderr,"[CONFIG] %s.%s type %i  not supported\n",prefix, cfgoptions[i].optname,cfgoptions[i].type);
            fatalerror=1;
        break;
      } /* switch on param type */
    if (defval == 1) {
    	numdefvals++;
        cfgoptions[i].paramflags = cfgoptions[i].paramflags |  PARAMFLAG_PARAMSETDEF;
    }
  } /* for loop on options */
  printf("[CONFIG] %s: %i/%i parameters successfully set \n",
         ((prefix == NULL)?"(root)":prefix), 
         numdefvals,numoptions );
  if (fatalerror == 1) {
      fprintf(stderr,"[CONFIG] fatal errors found when assigning %s parameters \n",
             prefix);
  }
  return numdefvals;
}

configmodule_interface_t *load_configmodule(int argc, char **argv)
{
char *cfgparam=NULL;
char *modeparams=NULL;
char *cfgmode=NULL;
char *strtokctx=NULL;
char *atoken;
uint32_t tmpflags=0;
int i;
 
/* first parse the command line to look for the -O option */
  for (i = 0;i<argc;i++) {
       if (strlen(argv[i]) < 2) continue;
       if ( argv[i][1] == 'O' && i < (argc -1)) {
          cfgparam = argv[i+1]; 
       } 
       if ( strstr(argv[i], "help_config") != NULL  ) {
          config_printhelp(Config_Params,CONFIG_PARAMLENGTH(Config_Params));
          exit(0);
       }
       if ( (strcmp(argv[i]+1, "h") == 0) || (strstr(argv[i]+1, "help_") != NULL ) ) {
          tmpflags = CONFIG_HELP; 
       }            
   }

/* look for the OAI_CONFIGMODULE environement variable */
  if ( cfgparam == NULL ) {
     cfgparam = getenv("OAI_CONFIGMODULE");
     }

/* default */
  if (cfgparam == NULL) {
    tmpflags = tmpflags | CONFIG_NOOOPT;
    cfgparam = CONFIG_CMDLINEONLY ":dbgl0" ;         
  }
   
/* parse the config parameters to set the config source */
   i = sscanf(cfgparam,"%m[^':']:%ms",&cfgmode,&modeparams);
   if (i< 0) {
       fprintf(stderr,"[CONFIG] %s, %d, sscanf error parsing config source  %s: %s\n", __FILE__, __LINE__,cfgparam, strerror(errno));
       exit(-1);
   }
   else if ( i == 1 ) {
  /* -O argument doesn't contain ":" separator, assume -O <conf file> option, default cfgmode to libconfig
     with one parameter, the path to the configuration file */
       modeparams=cfgmode;
       cfgmode=strdup(CONFIG_LIBCONFIGFILE);
   }

   cfgptr = malloc(sizeof(configmodule_interface_t));
   memset(cfgptr,0,sizeof(configmodule_interface_t));

   cfgptr->rtflags = cfgptr->rtflags | tmpflags;
   cfgptr->argc   = argc;
   cfgptr->argv   = argv; 
   cfgptr->cfgmode=strdup(cfgmode);
   cfgptr->num_cfgP=0;
   atoken=strtok_r(modeparams,":",&strtokctx);     
   while ( cfgptr->num_cfgP< CONFIG_MAX_OOPT_PARAMS && atoken != NULL) {
       /* look for debug level in the config parameters, it is commom to all config mode 
          and will be removed frome the parameter array passed to the shared module */
       char *aptr;
       aptr=strcasestr(atoken,"dbgl");
       if (aptr != NULL) {
           cfgptr->rtflags = cfgptr->rtflags | strtol(aptr+4,NULL,0);

       } else {
           cfgptr->cfgP[cfgptr->num_cfgP] = strdup(atoken);
           cfgptr->num_cfgP++;
       }
       atoken = strtok_r(NULL,":",&strtokctx);
   }

   
   printf("[CONFIG] get parameters from %s ",cfgmode);
   for (i=0;i<cfgptr->num_cfgP; i++) {
        printf("%s ",cfgptr->cfgP[i]); 
   }
   printf(", debug flags: 0x%08x\n",cfgptr->rtflags);

   if (strstr(cfgparam,CONFIG_CMDLINEONLY) == NULL) {
      i=load_config_sharedlib(cfgptr);
      if (i ==  0) {
         printf("[CONFIG] config module %s loaded\n",cfgmode);
         Config_Params[CONFIGPARAM_DEBUGFLAGS_IDX].uptr=&(cfgptr->rtflags);
         config_get(Config_Params,CONFIG_PARAMLENGTH(Config_Params), CONFIG_SECTIONNAME );
      } else {
         fprintf(stderr,"[CONFIG] %s %d config module \"%s\" couldn't be loaded\n", __FILE__, __LINE__,cfgmode);
         cfgptr->rtflags = cfgptr->rtflags | CONFIG_HELP | CONFIG_ABORT;
      } 
   } else {
      cfgptr->init = (configmodule_initfunc_t)nooptfunc;
      cfgptr->get = config_cmdlineonly_get;
      cfgptr->getlist = config_cmdlineonly_getlist;
      cfgptr->end = (configmodule_endfunc_t)nooptfunc;
   }

  

   if (modeparams != NULL) free(modeparams);
   if (cfgmode != NULL) free(cfgmode);
   if (CONFIG_ISFLAGSET(CONFIG_ABORT)) {
       config_printhelp(Config_Params,CONFIG_PARAMLENGTH(Config_Params));
//       exit(-1);   
   }
   return cfgptr;
}


/* free memory allocated when reading parameters */
/* config module could be initialized again after this call */
void end_configmodule(void)
{ 
  if (cfgptr != NULL) {
      if (cfgptr->end != NULL) {
         printf ("[CONFIG] calling config module end function...\n"); 
         cfgptr->end();
      }

      printf ("[CONFIG] free %u config value pointers\n",cfgptr->numptrs);  
      for(int i=0; i<cfgptr->numptrs ; i++) {
          if (cfgptr->ptrs[i] != NULL) {
             free(cfgptr->ptrs[i]);
             cfgptr->ptrs[i]=NULL;
          }
      }
      cfgptr->numptrs=0;
  }
}

/* free all memory used by config module */
/* should be called only at program exit */
void free_configmodule(void)
{ 
  if (cfgptr != NULL) {
      end_configmodule();
      if( cfgptr->cfgmode != NULL) free(cfgptr->cfgmode);
      printf ("[CONFIG] free %u config parameter pointers\n",cfgptr->num_cfgP);  
      for (int i=0; i<cfgptr->num_cfgP; i++) {
          if ( cfgptr->cfgP[i] != NULL) free(cfgptr->cfgP[i]);
          }


  free(cfgptr);
  cfgptr=NULL;
  }
}