#include <vlib/vlib.h>
#include <vnet/plugin/plugin.h>
#include <snort/snort.h>
#include <vlibapi/api_types.h>

#include <snort/snort.api_enum.h>
#include <snort/snort.api_types.h>

#include <vlibmemory/api.h>
#include <vnet/ip/ip_types_api.h>
#include <vnet/format_fns.h>
#include <vnet/api_errno.h>

/**
 * Base message ID fot the plugin
 */
static u32 snort_base_msg_id;
#define REPLY_MSG_ID_BASE snort_base_msg_id

#include <vlibapi/api_helper_macros.h>

#include <vnet/vnet.h>

#include <vlibapi/api.h>
#include <sys/eventfd.h>

VLIB_REGISTER_LOG_CLASS (snort_log, static) = {
  .class_name = "snort",
};

#define log_debug(fmt, ...) vlib_log_debug (snort_log.class, fmt, __VA_ARGS__)
#define log_err(fmt, ...)   vlib_log_err (snort_log.class, fmt, __VA_ARGS__)

static void
vl_api_snort_instance_create_t_handler (vl_api_snort_instance_create_t *mp)
{
  vlib_main_t *vm = vlib_get_main ();
  vl_api_snort_instance_create_reply_t *rmp;
  char *name = vl_api_from_api_to_new_c_string (&mp->name);
  u32 queue_sz = clib_net_to_host_u32 (mp->queue_size);
  u8 drop_on_disconnect = mp->drop_on_disconnect;
  int rv = 0;
  u32 instance_index = ~0;
  snort_instance_t *si;

  rv =
    snort_instance_create (vm, name, min_log2 (queue_sz), drop_on_disconnect);

  if ((si = snort_get_instance_by_name (name)))
    {
      instance_index = si->index;
    }

  REPLY_MACRO2 (VL_API_SNORT_INSTANCE_CREATE_REPLY, ({
		  rmp->instance_index = clib_host_to_net_u32 (instance_index);
		}));
}

static void
vl_api_snort_instance_delete_t_handler (vl_api_snort_instance_delete_t *mp)
{
  vlib_main_t *vm = vlib_get_main ();
  vl_api_snort_instance_delete_reply_t *rmp;
  u32 instance_index = clib_net_to_host_u32 (mp->instance_index);
  int rv;

  rv = snort_instance_delete (vm, instance_index);

  REPLY_MACRO (VL_API_SNORT_INSTANCE_DELETE_REPLY);
}

static void
vl_api_snort_interface_attach_t_handler (vl_api_snort_interface_attach_t *mp)
{
  vlib_main_t *vm = vlib_get_main ();
  vl_api_snort_interface_attach_reply_t *rmp;
  u32 instance_index = clib_net_to_host_u32 (mp->instance_index);
  snort_instance_t *instance = 0;
  u32 sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);
  u8 snort_dir = mp->snort_dir;
  int rv = VNET_API_ERROR_NO_SUCH_ENTRY;

  if (sw_if_index == INDEX_INVALID)
    rv = VNET_API_ERROR_NO_MATCHING_INTERFACE;
  else
    {
      instance = snort_get_instance_by_index (instance_index);
      if (instance)
	rv = snort_interface_enable_disable (vm, (char *) instance->name,
					     sw_if_index, 1 /* is_enable */,
					     snort_dir);
    }

  REPLY_MACRO (VL_API_SNORT_INTERFACE_ATTACH_REPLY);
}

static void
send_snort_instance_details (const snort_instance_t *instance,
			     vl_api_registration_t *rp, u32 context)
{
  vl_api_snort_instance_details_t *rmp;
  u32 name_len = vec_len (instance->name);

  REPLY_MACRO_DETAILS5 (
    VL_API_SNORT_INSTANCE_DETAILS, name_len, rp, context, ({
      rmp->instance_index = clib_host_to_net_u32 (instance->index);
      vl_api_vec_to_api_string (instance->name, &rmp->name);
      rmp->snort_client_index = clib_host_to_net_u32 (instance->client_index);
      rmp->shm_size = clib_host_to_net_u32 (instance->shm_size);
      rmp->shm_fd = clib_host_to_net_u32 (instance->shm_fd);
      rmp->drop_on_disconnect = instance->drop_on_disconnect;
    }));
}

static void
vl_api_snort_instance_get_t_handler (vl_api_snort_instance_get_t *mp)
{
  snort_main_t *sm = snort_get_main ();
  snort_instance_t *instance = 0;
  vl_api_snort_instance_get_reply_t *rmp;
  u32 instance_index;
  int rv = 0;

  instance_index = clib_net_to_host_u32 (mp->instance_index);

  if (instance_index == INDEX_INVALID)
    {
      /* clang-format off */
      REPLY_AND_DETAILS_MACRO (
        VL_API_SNORT_INSTANCE_GET_REPLY, sm->instances, ({
	  instance = pool_elt_at_index (sm->instances, cursor);
          send_snort_instance_details (instance, rp, mp->context);
        }));
      /* clang-format on */
    }
  else
    {
      instance = snort_get_instance_by_index (instance_index);

      if (instance)
	{
	  vl_api_registration_t *rp =
	    vl_api_client_index_to_registration (mp->client_index);

	  if (rp == NULL)
	    {
	      return;
	    }

	  send_snort_instance_details (instance, rp, mp->context);
	}
      else
	{
	  rv = VNET_API_ERROR_NO_SUCH_ENTRY;
	}

      /* clang-format off */
      REPLY_MACRO2 (VL_API_SNORT_INSTANCE_GET_REPLY, ({
        rmp->cursor = INDEX_INVALID;
      }));
      /* clang-format on */
    }
}

static void
send_snort_interface_details (u32 sw_if_index, u32 instance_index,
			      vl_api_registration_t *rp, u32 context)
{
  vl_api_snort_interface_details_t *rmp;

  if (instance_index != ~0)
    {
      REPLY_MACRO_DETAILS4 (VL_API_SNORT_INTERFACE_DETAILS, rp, context, ({
			      rmp->instance_index =
				clib_host_to_net_u32 (instance_index);
			      rmp->sw_if_index =
				clib_host_to_net_u32 (sw_if_index);
			    }));
    }
}

static void
vl_api_snort_interface_get_t_handler (vl_api_snort_interface_get_t *mp)
{
  snort_main_t *sm = snort_get_main ();
  vl_api_snort_interface_get_reply_t *rmp;
  u32 sw_if_index;
  u32 *index;
  int rv = 0;

  sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);

  if (sw_if_index == INDEX_INVALID)
    {
      /* clang-format off */
      if (vec_len (sm->instance_by_sw_if_index) == 0)
	{
	  REPLY_MACRO2 (VL_API_SNORT_INTERFACE_GET_REPLY, ({ rmp->cursor = ~0; }));
	  return;
	}

      REPLY_AND_DETAILS_VEC_MACRO(
	VL_API_SNORT_INTERFACE_GET_REPLY,
	sm->instance_by_sw_if_index,
	mp, rmp, rv, ({
          index = vec_elt_at_index (sm->instance_by_sw_if_index, cursor);
          send_snort_interface_details (cursor, *index, rp, mp->context);
	}))
      /* clang-format on */
    }
  else
    {
      index = vec_elt_at_index (sm->instance_by_sw_if_index, sw_if_index);
      if (snort_get_instance_by_index (index[0]))
	{
	  vl_api_registration_t *rp =
	    vl_api_client_index_to_registration (mp->client_index);

	  if (rp == NULL)
	    {
	      return;
	    }

	  send_snort_interface_details (sw_if_index, *index, rp, mp->context);
	}
      else
	{
	  rv = VNET_API_ERROR_NO_SUCH_ENTRY;
	}

      /* clang-format off */
      REPLY_MACRO2 (VL_API_SNORT_INTERFACE_GET_REPLY, ({
        rmp->cursor = INDEX_INVALID;
      }));
      /* clang-format on */
    }
}

static void
send_snort_client_details (const snort_client_t *client,
			   vl_api_registration_t *rp, u32 context)
{
  snort_main_t *sm = snort_get_main ();
  vl_api_snort_client_details_t *rmp;
  snort_instance_t *instance;

  if (client->instance_index == ~0)
    {
      return;
    }

  instance = pool_elt_at_index (sm->instances, client->instance_index);
  if (instance)
    {
      REPLY_MACRO_DETAILS4 (VL_API_SNORT_CLIENT_DETAILS, rp, context, ({
			      rmp->instance_index =
				clib_host_to_net_u32 (client->instance_index);
			      rmp->client_index =
				clib_host_to_net_u32 (client - sm->clients);
			    }));
    }
}

static void
vl_api_snort_client_get_t_handler (vl_api_snort_client_get_t *mp)
{
  snort_main_t *sm = snort_get_main ();
  snort_client_t *client;
  vl_api_snort_client_get_reply_t *rmp;
  u32 client_index;
  int rv = 0;

  client_index = clib_net_to_host_u32 (mp->snort_client_index);

  if (client_index == INDEX_INVALID)
    {
      /* clang-format off */
      REPLY_AND_DETAILS_MACRO (
        VL_API_SNORT_CLIENT_GET_REPLY, sm->clients, ({
          client = pool_elt_at_index (sm->clients, cursor);
          send_snort_client_details (client, rp, mp->context);
        }));
      /* clang-format on */
    }
  else
    {
      client = pool_elt_at_index (sm->clients, client_index);

      if (client)
	{
	  vl_api_registration_t *rp =
	    vl_api_client_index_to_registration (mp->client_index);

	  if (rp == NULL)
	    {
	      return;
	    }

	  send_snort_client_details (client, rp, mp->context);
	}
      else
	{
	  rv = VNET_API_ERROR_NO_SUCH_ENTRY;
	}

      /* clang-format off */
      REPLY_MACRO2 (VL_API_SNORT_CLIENT_GET_REPLY, ({
        rmp->cursor = INDEX_INVALID;
      }));
      /* clang-format on */
    }
}

static void
vl_api_snort_client_disconnect_t_handler (vl_api_snort_client_disconnect_t *mp)
{
  vlib_main_t *vm = vlib_get_main ();
  snort_main_t *sm = snort_get_main ();
  snort_client_t *client;
  vl_api_snort_client_disconnect_reply_t *rmp;
  u32 client_index = clib_net_to_host_u32 (mp->snort_client_index);
  int rv = 0;

  if (pool_is_free_index (sm->clients, client_index))
    {
      rv = VNET_API_ERROR_NO_SUCH_ENTRY;
    }
  else
    {
      client = pool_elt_at_index (sm->clients, client_index);
      rv = snort_instance_disconnect (vm, client->instance_index);
    }

  REPLY_MACRO (VL_API_SNORT_CLIENT_DISCONNECT_REPLY);
}

static void
vl_api_snort_instance_disconnect_t_handler (
  vl_api_snort_instance_disconnect_t *mp)
{
  vlib_main_t *vm = vlib_get_main ();
  vl_api_snort_instance_disconnect_reply_t *rmp;
  u32 instance_index = clib_net_to_host_u32 (mp->instance_index);
  int rv = snort_instance_disconnect (vm, instance_index);

  REPLY_MACRO (VL_API_SNORT_INSTANCE_DISCONNECT_REPLY);
}

static void
vl_api_snort_interface_detach_t_handler (vl_api_snort_interface_detach_t *mp)
{
  vlib_main_t *vm = vlib_get_main ();
  vl_api_snort_interface_detach_reply_t *rmp;
  u32 sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);
  int rv = VNET_API_ERROR_NO_MATCHING_INTERFACE;

  if (sw_if_index != INDEX_INVALID)
    rv = snort_interface_enable_disable (vm, NULL, sw_if_index,
					 0 /* is_enable */, SNORT_INOUT);

  REPLY_MACRO (VL_API_SNORT_INTERFACE_DETACH_REPLY);
}

static void
vl_api_snort_input_mode_get_t_handler (vl_api_snort_input_mode_get_t *mp)
{
  snort_main_t *sm = &snort_main;
  vl_api_snort_input_mode_get_reply_t *rmp;
  int rv = 0;

  REPLY_MACRO2 (VL_API_SNORT_INPUT_MODE_GET_REPLY, ({
		  rmp->snort_mode = clib_host_to_net_u32 (sm->input_mode);
		}));
}

static void
vl_api_snort_input_mode_set_t_handler (vl_api_snort_input_mode_set_t *mp)
{
  vlib_main_t *vm = vlib_get_main ();
  vl_api_snort_input_mode_set_reply_t *rmp;
  u8 mode = mp->input_mode;
  int rv = 0;

  if (mode != VLIB_NODE_STATE_INTERRUPT && mode != VLIB_NODE_STATE_POLLING)
    {
      clib_error_return (0, "invalid input mode %u", mode);
    }
  snort_set_node_mode (vm, mode);

  REPLY_MACRO (VL_API_SNORT_INPUT_MODE_SET_REPLY);
}

/* API definitions */
#include <snort/snort.api.c>

clib_error_t *
snort_init_api (vlib_main_t *vm)
{
  /* Add our API messages to the global name_crc hash table */
  snort_base_msg_id = setup_message_id_table ();

  return NULL;
}

VLIB_INIT_FUNCTION (snort_init_api);