/**
 * collectd - src/protocols.c
 * Copyright (C) 2009,2010  Florian octo Forster
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *   Florian octo Forster <octo at collectd.org>
 **/

#include "collectd.h"

#include "plugin.h"
#include "utils/common/common.h"
#include "utils/ignorelist/ignorelist.h"

#if !KERNEL_LINUX
#error "No applicable input method."
#endif

#define SNMP_FILE "/proc/net/snmp"
#define NETSTAT_FILE "/proc/net/netstat"

/*
 * Global variables
 */
static const char *config_keys[] = {
    "Value",
    "IgnoreSelected",
};
static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);

static ignorelist_t *values_list;

/*
 * Functions
 */
static void submit(const char *protocol_name, const char *str_key,
                   const char *str_value) {
  value_t value;
  value_list_t vl = VALUE_LIST_INIT;
  int status;

  status = parse_value(str_value, &value, DS_TYPE_DERIVE);
  if (status != 0) {
    return;
  }

  vl.values = &value;
  vl.values_len = 1;
  sstrncpy(vl.plugin, "protocols", sizeof(vl.plugin));
  sstrncpy(vl.plugin_instance, protocol_name, sizeof(vl.plugin_instance));
  sstrncpy(vl.type, "protocol_counter", sizeof(vl.type));
  sstrncpy(vl.type_instance, str_key, sizeof(vl.type_instance));

  plugin_dispatch_values(&vl);
} /* void submit */

static int read_file(const char *path) {
  FILE *fh;
  char key_buffer[4096];
  char value_buffer[4096];
  char *key_ptr;
  char *value_ptr;
  char *key_fields[256];
  char *value_fields[256];
  int key_fields_num;
  int value_fields_num;
  int status;
  int i;

  fh = fopen(path, "r");
  if (fh == NULL) {
    ERROR("protocols plugin: fopen (%s) failed: %s.", path, STRERRNO);
    return -1;
  }

  status = -1;
  while (42) {
    clearerr(fh);
    key_ptr = fgets(key_buffer, sizeof(key_buffer), fh);
    if (key_ptr == NULL) {
      if (feof(fh) != 0) {
        status = 0;
        break;
      } else if (ferror(fh) != 0) {
        ERROR("protocols plugin: Reading from %s failed.", path);
        break;
      } else {
        ERROR("protocols plugin: fgets failed for an unknown reason.");
        break;
      }
    } /* if (key_ptr == NULL) */

    value_ptr = fgets(value_buffer, sizeof(value_buffer), fh);
    if (value_ptr == NULL) {
      ERROR("protocols plugin: read_file (%s): Could not read values line.",
            path);
      break;
    }

    key_ptr = strchr(key_buffer, ':');
    if (key_ptr == NULL) {
      ERROR("protocols plugin: Could not find protocol name in keys line.");
      break;
    }
    *key_ptr = 0;
    key_ptr++;

    value_ptr = strchr(value_buffer, ':');
    if (value_ptr == NULL) {
      ERROR("protocols plugin: Could not find protocol name "
            "in values line.");
      break;
    }
    *value_ptr = 0;
    value_ptr++;

    if (strcmp(key_buffer, value_buffer) != 0) {
      ERROR("protocols plugin: Protocol names in keys and values lines "
            "don't match: `%s' vs. `%s'.",
            key_buffer, value_buffer);
      break;
    }

    key_fields_num =
        strsplit(key_ptr, key_fields, STATIC_ARRAY_SIZE(key_fields));
    value_fields_num =
        strsplit(value_ptr, value_fields, STATIC_ARRAY_SIZE(value_fields));

    if (key_fields_num != value_fields_num) {
      ERROR("protocols plugin: Number of fields in keys and values lines "
            "don't match: %i vs %i.",
            key_fields_num, value_fields_num);
      break;
    }

    for (i = 0; i < key_fields_num; i++) {
      if (values_list != NULL) {
        char match_name[2 * DATA_MAX_NAME_LEN];

        ssnprintf(match_name, sizeof(match_name), "%s:%s", key_buffer,
                  key_fields[i]);

        if (ignorelist_match(values_list, match_name))
          continue;
      } /* if (values_list != NULL) */

      submit(key_buffer, key_fields[i], value_fields[i]);
    } /* for (i = 0; i < key_fields_num; i++) */
  }   /* while (42) */

  fclose(fh);

  return status;
} /* int read_file */

static int protocols_read(void) {
  int status;
  int success = 0;

  status = read_file(SNMP_FILE);
  if (status == 0)
    success++;

  status = read_file(NETSTAT_FILE);
  if (status == 0)
    success++;

  if (success == 0)
    return -1;

  return 0;
} /* int protocols_read */

static int protocols_config(const char *key, const char *value) {
  if (values_list == NULL)
    values_list = ignorelist_create(/* invert = */ 1);

  if (strcasecmp(key, "Value") == 0) {
    ignorelist_add(values_list, value);
  } else if (strcasecmp(key, "IgnoreSelected") == 0) {
    int invert = 1;
    if (IS_TRUE(value))
      invert = 0;
    ignorelist_set_invert(values_list, invert);
  } else {
    return -1;
  }

  return 0;
} /* int protocols_config */

void module_register(void) {
  plugin_register_config("protocols", protocols_config, config_keys,
                         config_keys_num);
  plugin_register_read("protocols", protocols_read);
} /* void module_register */
