/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2007
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    General Public License for more details.
  
    You should have received a copy of the GNU General Public License
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-config.c :: Midnight Code config file library		*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <netinet/in.h>
#include <sys/stat.h>

#include "preferences.h"
#include "midnightcode-parse.h"
#include "midnightcode-list.h"
#include "midnightcode-config.h"
#include "midnightcode-interface.h"
#include "midnightcode-disk.h"
#include "midnightcode-route.h"



int
midnightcode_config_parseline(char *config_buf, int config_buf_max,
                              struct midnightcode_config_head_t * elements,
                              int type, int options) {
  char *args[64];
  int  arg_max, arg_index;
  char *config_buf_p;
  int  config_buf_offset;
  char *parse_buf;
  int  parse_buf_size;
  int  config_command, config_structure, config_section;
  int  config_variable, config_value;
  int  exists;
  char *tmp_value_p;
  struct midnightcode_config_element_t * now_element;


  if(config_buf_max < 1)
    return -1;
  parse_buf_size = config_buf_max + 1;
  parse_buf = malloc(parse_buf_size);
  if(!parse_buf)
    return -1;

  config_buf_p = config_buf;
  config_buf_offset = strlen(config_buf) - 1;
  config_buf_p += config_buf_offset;
  if(*config_buf_p == '\n')
    *config_buf_p = '\0';
  arg_max = midnightcode_parse_strtoarg(config_buf, config_buf_max,
                                        args, 63);

  config_command = 0;
  config_structure = 0;
  config_section = 0;
  config_variable = 0;
  config_value = 0;
  switch(type) {
    case MIDNIGHTCODE_CONFIG_TYPE_NEWSET:
      config_command = 1;
      if(strcmp(args[config_command], "new") == 0) {
        config_structure = 2;
        config_section = 3;
        config_variable = 0;
        config_value = 0;
      } else {
        config_structure = 2;
        config_section = 3;
        config_variable = 4;
        config_value = 5;
      }
      break;

    case MIDNIGHTCODE_CONFIG_TYPE_INI:
      break;

    default:
      free(parse_buf);
      return MIDNIGHTCODE_CONFIG_ERROR_WRONGTYPE;
      break;
  }

  if(options & MIDNIGHTCODE_CONFIG_OPTION_MERGEARGS &&
     config_value > 0 && arg_max > config_value) {
    memset(parse_buf, 0, parse_buf_size);
    strncat(parse_buf, args[config_value],
            parse_buf_size - strlen(parse_buf) - 1);
    for(arg_index = config_value + 1;
        arg_index <= arg_max && args[arg_index]; arg_index++) {
      strncat(parse_buf, " ", parse_buf_size - strlen(parse_buf) - 1);
      strncat(parse_buf, args[arg_index],
              parse_buf_size - strlen(parse_buf) - 1);
    }
    args[config_value] = parse_buf;
    args[config_value + 1] = NULL;
  }

  switch(type) {
    case MIDNIGHTCODE_CONFIG_TYPE_NEWSET:
      if(strcmp(args[config_command], "new") == 0) {

        if(!(options & MIDNIGHTCODE_CONFIG_OPTION_AUTONEW)) {
          exists = 0;
          midnightcode_list_for_each(now_element, elements) {
            if(strcmp(args[config_command], now_element->command) == 0 &&
               strcmp(args[config_structure], now_element->structure) == 0 &&
               strcmp(args[config_section], now_element->section) == 0) {
              exists |= 1;
              break;
            }
          }

          if(exists) {
            free(parse_buf);
            return MIDNIGHTCODE_CONFIG_ERROR_EXISTS;
          }

          /* TODO: null check on malloc */
          now_element = malloc(sizeof(struct midnightcode_config_element_t));
          memset(now_element, 0,
                 sizeof(struct midnightcode_config_element_t));
          strncpy(now_element->command, args[config_command], 255);
          strncpy(now_element->structure, args[config_structure], 255);
          strncpy(now_element->section, args[config_section], 255);
          midnightcode_list_append(elements, now_element);

        }

      } else if(strcmp(args[config_command], "set") == 0) {
        exists = 0;
        midnightcode_list_for_each(now_element, elements) {
          if(strcmp(args[config_structure], now_element->structure) == 0 &&
             strcmp(args[config_section], now_element->section) == 0) {
            if(strcmp("new", now_element->command) == 0) {
              exists |= 1;
            }
            if(strcmp(args[config_command], now_element->command) == 0 &&
               strcmp(args[config_variable], now_element->variable) == 0) {
              exists |= 2;
              break;
            }
          }
        }

        if(!(options & MIDNIGHTCODE_CONFIG_OPTION_AUTONEW)) {
          if(!(exists & 1)) {
            free(parse_buf);
            return MIDNIGHTCODE_CONFIG_ERROR_NONEW;
          }
        }
        if(exists & 2) {
          free(parse_buf);
          return MIDNIGHTCODE_CONFIG_ERROR_EXISTS;
        }

        /* TODO: null check on malloc */
        now_element = malloc(sizeof(struct midnightcode_config_element_t));
        memset(now_element, 0,
               sizeof(struct midnightcode_config_element_t));
        strncpy(now_element->command, args[config_command], 255);
        strncpy(now_element->structure, args[config_structure], 255);
        strncpy(now_element->section, args[config_section], 255);
        strncpy(now_element->variable, args[config_variable], 255);
        if(config_value != 0 && strlen(args[config_value]) > 0) {
          tmp_value_p = malloc(strlen(args[config_value]) + 1);
          memset(tmp_value_p, 0, strlen(args[config_value]) + 1);
          strncpy(tmp_value_p, args[config_value],
                  strlen(args[config_value]) + 1);
          now_element->value = tmp_value_p;
          now_element->value_size = strlen(args[config_value]) + 1;
        }
        midnightcode_list_append(elements, now_element);

      } else {
        free(parse_buf);
        return MIDNIGHTCODE_CONFIG_ERROR_SYNTAX;

      }
      break;

    case MIDNIGHTCODE_CONFIG_TYPE_INI:
      break;

  }

  free(parse_buf);
  return MIDNIGHTCODE_CONFIG_ERROR_NONE;
}



int
midnightcode_config_read(struct midnightcode_config_head_t * elements,
                         char * config_buf, int config_buf_size,
                         int type, int options) {
  char *parse_buf;
  int  parse_buf_size;
  char *parse_ptr, *parse_ref;
  char *config_ptr, *config_ref;
  int config_len;
  int ret_i;
  int line;


  parse_buf_size = MIDNIGHTCODE_CONFIG_LINESIZE_MAX;
  parse_buf = malloc(parse_buf_size);
  if(!parse_buf)
    return -1;

  ret_i = 0;
  line = 0;
  parse_ptr = parse_buf;
  config_ptr = config_buf;
  while(config_ptr < (config_buf + config_buf_size)) {
    memset(parse_ptr, 0, parse_buf_size - (parse_ptr - parse_buf));
    config_ref = strstr(config_ptr, "\n");
    if(config_ref) {
      config_len = (int)(config_ref - config_ptr) + 1;
    } else {
      config_len = strlen(config_ptr);
      config_ref = config_ptr + config_len;
    }
    if(config_len < 0 ||
      config_len >= parse_buf_size - (parse_ptr - parse_buf)) {
      ret_i = -1;
      break;
    }
    if((config_ptr + config_len) > (config_buf + config_buf_size)) {
      config_len = config_buf + config_buf_size - config_ptr;
      config_ref = config_ptr + config_len;
    }
    /* TODO: evaulate while statement to include the cause of this condition */
    if(config_len == 0)
      break;

    memcpy(parse_ptr, config_ptr, config_len);
    config_ptr += config_len;
    line++;

    if(parse_buf[0] == '#' || parse_buf[0] == '\n' || parse_buf[0] == '\0') {
      parse_ptr = parse_buf;
      continue;
    }
    parse_ref = parse_ptr + strlen(parse_ptr);
    if(parse_ref > parse_ptr &&
        *--parse_ref == '\n' && *--parse_ref == '\\') {
      parse_ptr = parse_ref;
      continue;
    }

    if((ret_i = midnightcode_config_parseline(parse_buf, parse_buf_size - 1,
                                              elements, type, options)) < 0) {
      switch(ret_i) {
        case MIDNIGHTCODE_CONFIG_ERROR_NONE:
          break;

        case MIDNIGHTCODE_CONFIG_ERROR_SYNTAX:
          if(!(options & MIDNIGHTCODE_CONFIG_OPTION_SILENT))
            printf("Config file - Syntax error on line %d\n", line);
          break;

        case MIDNIGHTCODE_CONFIG_ERROR_NONEW:
          if(!(options & MIDNIGHTCODE_CONFIG_OPTION_SILENT))
            printf("Config file - No \"new\" for \"set\" on line %d\n", line);
          break;

        case MIDNIGHTCODE_CONFIG_ERROR_EXISTS:
          if(!(options & MIDNIGHTCODE_CONFIG_OPTION_SILENT))
            printf("Config file - Duplicate entry on line %d\n", line);
          break;

      }
      if(ret_i < 0)
        break;
    }
    parse_ptr = parse_buf;
  }

  free(parse_buf);
  return ret_i;
}



int
midnightcode_config_read_file(char *config_file,
                         struct midnightcode_config_head_t * elements,
                         int type, int options) {
  FILE *fp;
  char *config_buf;
  int config_buf_size;
  struct stat stat_buf;
  int ret_i;


  if((fp = fopen(config_file, "r")) == NULL)
    return -1;

  memset(&stat_buf, 0, sizeof(struct stat));
  if(stat(config_file, &stat_buf)) {
    fclose(fp);
    return -1;
  }

  config_buf_size = stat_buf.st_size;
  if(config_buf_size > MIDNIGHTCODE_CONFIG_FILESIZE_MAX) {
    fclose(fp);
    return -1;
  }
  config_buf = malloc(config_buf_size);
  if(!config_buf) {
    fclose(fp);
    return -1;
  }
  memset(config_buf, 0, config_buf_size);

  if(fread(config_buf, config_buf_size, 1, fp) != 1) {
    fclose(fp);
    free(config_buf);
    return -1;
  }

  fclose(fp);

  ret_i = midnightcode_config_read(elements, config_buf, config_buf_size,
    type, options);

  free(config_buf);

  return ret_i;
}



int
midnightcode_config_write(struct midnightcode_config_head_t * elements,
                          char ** config_buf, int * config_buf_size,
                          int type, int options) {
  struct midnightcode_config_element_t * now_element;
  char *config_ptr;
  char *parse_buf;
  int parse_buf_size;
  int ret_i;
  char structure_change[256];
  char section_change[256] = "\n";
  char old_structure[256];
  char old_section[256];
  int parse_len;
  int variable_offset;
  int value_quotes;


  if(!config_buf)
    return -1;
  if(!config_buf_size)
    return -1;

  ret_i = 0;

  parse_buf_size = 4096;
  parse_buf = malloc(parse_buf_size);
  if(!parse_buf)
    return -1;

  *config_buf_size = MIDNIGHTCODE_CONFIG_FILESIZE_MAX;
  *config_buf = malloc(*config_buf_size);
  if(!*config_buf) {
    free(parse_buf);
    return -1;
  }
  memset(*config_buf, 0, *config_buf_size);
  config_ptr = *config_buf;

  /* TODO: options - are they needed in this function? */
  if(options != 0)
    ret_i = 0;

  memset(old_structure, 0, sizeof(old_structure));
  memset(old_section, 0, sizeof(old_section));

  midnightcode_list_for_each(now_element, elements) {
    memset(parse_buf, 0, parse_buf_size);

    if(strcmp(now_element->structure, MIDNIGHTCODE_DISK_STRUCTURE) == 0 ||
      strcmp(now_element->structure, MIDNIGHTCODE_INTERFACE_STRUCTURE) == 0 ||
      strcmp(now_element->structure, MIDNIGHTCODE_ROUTE_STRUCTURE) == 0) {
      if(strcmp(old_section, now_element->section) != 0 ) {
        memset(old_section, 0, sizeof(old_section));
        strncpy(old_section, now_element->section,
                sizeof(old_section) - 1);
        /* TODO: Consider realloc to ensure correct sizing */
        if((*config_buf_size - strlen(*config_buf) - 1) <
          strlen(section_change)) {
          free(parse_buf);
          free(*config_buf);
          *config_buf_size = 0;
          return -1;
        }
        strncat(config_ptr, section_change,
          *config_buf_size - strlen(*config_buf) - 1);
        config_ptr += strlen(section_change);
      }
    }

    if(strcmp(old_structure, now_element->structure) != 0 ) {
      memset(old_structure, 0, sizeof(old_structure));
      strncpy(old_structure, now_element->structure,
              sizeof(old_structure) - 1);
      memset(structure_change, 0, sizeof(structure_change));
      snprintf(structure_change, sizeof(structure_change) - 1,
               "\n# Configuration Group: \"%s\"\n\n", now_element->structure);
      /* TODO: Consider realloc to ensure correct sizing */
      if((*config_buf_size - strlen(*config_buf) - 1) <
        strlen(structure_change)) {
        free(parse_buf);
        free(*config_buf);
        *config_buf_size = 0;
        return -1;
      }
      strncat(config_ptr, structure_change,
        *config_buf_size - strlen(*config_buf) - 1);
      config_ptr += strlen(structure_change);
    }

    switch(type) {
      case MIDNIGHTCODE_CONFIG_TYPE_NEWSET:
        strncat(parse_buf, now_element->command,
          parse_buf_size - strlen(parse_buf) - 1);
        strncat(parse_buf, " ", parse_buf_size - strlen(parse_buf) - 1);
        if(strcmp(now_element->command, "new") == 0) {
          strncat(parse_buf, now_element->structure,
            parse_buf_size - strlen(parse_buf) - 1);
          strncat(parse_buf, " ", parse_buf_size - strlen(parse_buf) - 1);
          strncat(parse_buf, now_element->section,
            parse_buf_size - strlen(parse_buf) - 1);
        } else {
          strncat(parse_buf, now_element->structure,
            parse_buf_size - strlen(parse_buf) - 1);
          strncat(parse_buf, " ", parse_buf_size - strlen(parse_buf) - 1);
          strncat(parse_buf, now_element->section,
            parse_buf_size - strlen(parse_buf) - 1);
          variable_offset = 25;
          if(strlen(parse_buf) < (unsigned int)variable_offset) {
            for(parse_len = strlen(parse_buf);
                parse_len < variable_offset;
                parse_len++)
              strncat(parse_buf, " ",
                parse_buf_size - strlen(parse_buf) - 1);
          }
          strncat(parse_buf, " ", parse_buf_size - strlen(parse_buf) - 1);
          strncat(parse_buf, now_element->variable,
            parse_buf_size - strlen(parse_buf) - 1);
          strncat(parse_buf, " ", parse_buf_size - strlen(parse_buf) - 1);

          if(now_element->value) {
            value_quotes = 0;
            if(midnightcode_parse_any(now_element->value, " "))
              value_quotes = 1;
            if(value_quotes)
              strncat(parse_buf, "\"",
                parse_buf_size - strlen(parse_buf) - 1);
            strncat(parse_buf, now_element->value,
              parse_buf_size - strlen(parse_buf) - 1);
            if(value_quotes)
              strncat(parse_buf, "\"",
                parse_buf_size - strlen(parse_buf) - 1);
          }
        }
        if(!strncat(parse_buf, "\n",
            parse_buf_size - strlen(parse_buf) - 1)) {
          free(parse_buf);
          free(*config_buf);
          *config_buf_size = 0;
          return -1;
        }
        break;

      case MIDNIGHTCODE_CONFIG_TYPE_INI:
        break;

      default:
        free(parse_buf);
        free(*config_buf);
        *config_buf_size = 0;
        return MIDNIGHTCODE_CONFIG_ERROR_WRONGTYPE;

    }

    /* TODO: Consider realloc to ensure correct sizing */
    if((*config_buf_size - strlen(*config_buf) - 1) < strlen(parse_buf)) {
      free(parse_buf);
      free(*config_buf);
      *config_buf_size = 0;
      return -1;
    }
    strncat(config_ptr, parse_buf, *config_buf_size - strlen(*config_buf) - 1);
    config_ptr += strlen(parse_buf);

  }
  free(parse_buf);

  if((int)(strlen(*config_buf) + 1) < *config_buf_size) {
    config_ptr = realloc(*config_buf, (strlen(*config_buf) + 1));
    if(config_ptr) {
      *config_buf = config_ptr;
      *config_buf_size = strlen(*config_buf) + 1;
    }
  }

  return ret_i;
}



int
midnightcode_config_write_file(char *config_file,
                          struct midnightcode_config_head_t * elements,
                          int type, int options) {
  FILE *fp;
  char *config_buf;
  int config_buf_size;
  int ret_i;


  ret_i = 0;

  /* TODO: options - are they needed in this function? */
  if(options != 0)
    ret_i = 0;

  if((fp = fopen(config_file, "w")) == NULL) {
    return -1;
  }

  if(midnightcode_config_write(elements, &config_buf, &config_buf_size,
    type, options) == 0) {
    if(config_buf_size > 0) {
      if(fputs(config_buf, fp) < 0 ) {
        ret_i = -1;
      }
    } else {
      ret_i = -1;
    }
  } else {
    ret_i = -1;
  }

  if(config_buf_size > 0)
    free(config_buf);

  fclose(fp);


  return ret_i;
}



int
midnightcode_config_del_structure(struct midnightcode_config_head_t * elements,
                                  char * structure) {
  struct midnightcode_config_element_t * now_element;
  struct midnightcode_config_element_t * old_element;


  if(!elements)
    return -1;

  if(strlen(structure) < 1 || strlen(structure) > 255)
    return -1;

  old_element = NULL;
  midnightcode_list_for_each(now_element, elements) {
    if(strcmp(now_element->structure, structure) == 0) {
      old_element = now_element;
      midnightcode_list_remove(elements, now_element);
      if(old_element != NULL) {
        if(old_element->value != NULL)
          free(old_element->value);
        free(old_element);
      }
      old_element = NULL;
    }
  }


  return 0;
}



int
midnightcode_config_get_sections(
                  struct midnightcode_config_head_t * config_elements,
                  struct midnightcode_config_section_head_t * section_elements,
                  char * structure) {
  struct midnightcode_config_element_t * config_element;
  struct midnightcode_config_section_element_t * section_element;
  char old_section[256];
  int sectn;


  if(!config_elements || !section_elements)
    return -1;

  if(strlen(structure) < 1 || strlen(structure) > 255)
    return -1;

  sectn = 0;
  memset(old_section, 0, sizeof(old_section));
  midnightcode_list_for_each(config_element, config_elements) {
    if(strcmp(config_element->structure, structure) == 0) {
      if(strcmp(old_section, config_element->section) != 0 ) {
        memset(old_section, 0, sizeof(old_section));
        strncpy(old_section, config_element->section, sizeof(old_section) - 1);

        section_element = malloc(sizeof(
                            struct midnightcode_config_section_element_t));
        memset(section_element, 0,
                 sizeof(struct midnightcode_config_section_element_t));
        strncpy(section_element->section, config_element->section, 255);
        midnightcode_list_append(section_elements, section_element);
        sectn++;
      }
    }
  }


  return sectn;
}



int
midnightcode_config_add_pair(
                     struct midnightcode_config_head_t *elements,
                     char * structure, char * section,
                     char * variable, void * value, int type) {
  struct midnightcode_config_element_t * now_element;
  char parse_buf[2048];
  char * tmp_value_p;


  if(!elements || !value)
    return -1;

  if(strlen(structure) < 1 || strlen(section) < 1 ||
     strlen(variable) < 1 )
    return -1;

  if(strlen(structure) > 255 || strlen(section) > 255 ||
     strlen(variable) > 255)
    return -1;

  midnightcode_list_for_each(now_element, elements) {
    if(strcmp(now_element->command, "set") == 0 &&
      strcmp(now_element->structure, structure) == 0 &&
      strcmp(now_element->section, section) == 0 &&
      strcmp(now_element->variable, variable) == 0) {
      return -2;
    }
  }

  memset(parse_buf, 0, sizeof(parse_buf));
  if(midnightcode_parse_vartostr(parse_buf, value, sizeof(parse_buf), 0, type))
    return -1;
  
  /* TODO: null check on malloc */
  now_element = malloc(sizeof(struct midnightcode_config_element_t));
  memset(now_element, 0, sizeof(struct midnightcode_config_element_t));
  strncpy(now_element->command, "set", 255);
  strncpy(now_element->structure, structure, 255);
  strncpy(now_element->section, section, 255);
  strncpy(now_element->variable, variable, 255);
  if(strlen(parse_buf) > 0) {
    tmp_value_p = malloc(strlen(parse_buf) + 1);
    memset(tmp_value_p, 0, strlen(parse_buf) + 1);
    strncpy(tmp_value_p, parse_buf, strlen(parse_buf) + 1);
    now_element->value = tmp_value_p;
    now_element->value_size = strlen(parse_buf) + 1;
  }
  midnightcode_list_append(elements, now_element);


  return 0;
}



int
midnightcode_config_get_pair(
                     struct midnightcode_config_head_t *elements,
                     char * structure, char * section,
                     char * variable, void * value, int length, int type) {
  struct midnightcode_config_element_t * now_element;
  char parse_buf[2048];
  int pair_found;


  if(!elements || !value)
    return -1;

  if(strlen(structure) < 1 || strlen(section) < 1 ||
     strlen(variable) < 1 )
    return -1;

  if(strlen(structure) > 255 || strlen(section) > 255 ||
     strlen(variable) > 255)
    return -1;

  pair_found = 0;
  midnightcode_list_for_each(now_element, elements) {
    if(strcmp(now_element->command, "set") == 0 &&
      strcmp(now_element->structure, structure) == 0 &&
      strcmp(now_element->section, section) == 0 &&
      strcmp(now_element->variable, variable) == 0) {
      pair_found = 1;
      break;
    }
  }
  if(!pair_found)
    return -2;

  memset(parse_buf, 0, sizeof(parse_buf));
  if(!now_element->value)
    return 0;
  if(midnightcode_parse_strtovar(value, now_element->value, length, 0, type))
    return -1;

  
  return 0;
}



