/*
 * Copyright (c) 2017 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

#include <vnet/session/application.h>
#include <vnet/session/session.h>

/*
 * Pool from which we allocate all applications
 */
static application_t *app_pool;

/*
 * Hash table of apps by api client index
 */
static uword *app_by_api_client_index;

int
application_api_queue_is_full (application_t * app)
{
  unix_shared_memory_queue_t *q;

  /* builtin servers are always OK */
  if (app->api_client_index == ~0)
    return 0;

  q = vl_api_client_index_to_input_queue (app->api_client_index);
  if (!q)
    return 1;

  if (q->cursize == q->maxsize)
    return 1;
  return 0;
}

static void
application_table_add (application_t * app)
{
  hash_set (app_by_api_client_index, app->api_client_index, app->index);
}

static void
application_table_del (application_t * app)
{
  hash_unset (app_by_api_client_index, app->api_client_index);
}

application_t *
application_lookup (u32 api_client_index)
{
  uword *p;
  p = hash_get (app_by_api_client_index, api_client_index);
  if (p)
    return application_get (p[0]);

  return 0;
}

void
application_del (application_t * app)
{
  session_manager_main_t *smm = vnet_get_session_manager_main ();
  api_main_t *am = &api_main;
  void *oldheap;
  session_manager_t *sm;

  if (app->mode == APP_SERVER)
    {
      sm = session_manager_get (app->session_manager_index);
      session_manager_del (smm, sm);
    }

  /* Free the event fifo in the /vpe-api shared-memory segment */
  oldheap = svm_push_data_heap (am->vlib_rp);
  if (app->event_queue)
    unix_shared_memory_queue_free (app->event_queue);
  svm_pop_heap (oldheap);

  application_table_del (app);

  pool_put (app_pool, app);
}

static void
application_verify_cb_fns (application_type_t type, session_cb_vft_t * cb_fns)
{
  if (type == APP_SERVER && cb_fns->session_accept_callback == 0)
    clib_warning ("No accept callback function provided");
  if (type == APP_CLIENT && cb_fns->session_connected_callback == 0)
    clib_warning ("No session connected callback function provided");
  if (cb_fns->session_disconnect_callback == 0)
    clib_warning ("No session disconnect callback function provided");
  if (cb_fns->session_reset_callback == 0)
    clib_warning ("No session reset callback function provided");
}

application_t *
application_new (application_type_t type, session_type_t sst,
		 u32 api_client_index, u32 flags, session_cb_vft_t * cb_fns)
{
  session_manager_main_t *smm = vnet_get_session_manager_main ();
  api_main_t *am = &api_main;
  application_t *app;
  void *oldheap;
  session_manager_t *sm;

  pool_get (app_pool, app);
  memset (app, 0, sizeof (*app));

  /* Allocate event fifo in the /vpe-api shared-memory segment */
  oldheap = svm_push_data_heap (am->vlib_rp);

  /* Allocate server event queue */
  app->event_queue =
    unix_shared_memory_queue_init (128 /* nels $$$$ config */ ,
				   sizeof (session_fifo_event_t),
				   0 /* consumer pid */ ,
				   0
				   /* (do not) signal when queue non-empty */
    );

  svm_pop_heap (oldheap);

  /* If a server, allocate session manager */
  if (type == APP_SERVER)
    {
      pool_get (smm->session_managers, sm);
      memset (sm, 0, sizeof (*sm));

      app->session_manager_index = sm - smm->session_managers;
    }
  else if (type == APP_CLIENT)
    {
      /* Allocate connect session manager if needed */
      if (smm->connect_manager_index[sst] == INVALID_INDEX)
	connects_session_manager_init (smm, sst);
      app->session_manager_index = smm->connect_manager_index[sst];
    }

  app->mode = type;
  app->index = application_get_index (app);
  app->session_type = sst;
  app->api_client_index = api_client_index;
  app->flags = flags;
  app->cb_fns = *cb_fns;

  /* Check that the obvious things are properly set up */
  application_verify_cb_fns (type, cb_fns);

  /* Add app to lookup by api_client_index table */
  application_table_add (app);

  return app;
}

application_t *
application_get (u32 index)
{
  return pool_elt_at_index (app_pool, index);
}

application_t *
application_get_if_valid (u32 index)
{
  if (pool_is_free_index (app_pool, index))
    return 0;

  return pool_elt_at_index (app_pool, index);
}

u32
application_get_index (application_t * app)
{
  return app - app_pool;
}

int
application_server_init (application_t * server, u32 segment_size,
			 u32 add_segment_size, u32 rx_fifo_size,
			 u32 tx_fifo_size, u8 ** segment_name)
{
  session_manager_main_t *smm = vnet_get_session_manager_main ();
  session_manager_t *sm;
  int rv;

  sm = session_manager_get (server->session_manager_index);

  /* Add first segment */
  if ((rv = session_manager_add_first_segment (smm, sm, segment_size,
					       segment_name)))
    {
      return rv;
    }

  /* Setup session manager */
  sm->add_segment_size = add_segment_size;
  sm->rx_fifo_size = rx_fifo_size;
  sm->tx_fifo_size = tx_fifo_size;
  sm->add_segment = sm->add_segment_size != 0;
  return 0;
}

u8 *
format_application_server (u8 * s, va_list * args)
{
  application_t *srv = va_arg (*args, application_t *);
  int verbose = va_arg (*args, int);
  vl_api_registration_t *regp;
  stream_session_t *listener;
  u8 *server_name, *str, *seg_name;
  u32 segment_size;

  if (srv == 0)
    {
      if (verbose)
	s = format (s, "%-40s%-20s%-15s%-15s%-10s", "Connection", "Server",
		    "Segment", "API Client", "Cookie");
      else
	s = format (s, "%-40s%-20s", "Connection", "Server");

      return s;
    }

  regp = vl_api_client_index_to_registration (srv->api_client_index);
  if (!regp)
    server_name = format (0, "builtin-%d%c", srv->index, 0);
  else
    server_name = regp->name;

  listener = stream_session_listener_get (srv->session_type,
					  srv->session_index);
  str = format (0, "%U", format_stream_session, listener, verbose);

  session_manager_get_segment_info (listener->server_segment_index, &seg_name,
				    &segment_size);
  if (verbose)
    {
      s = format (s, "%-40s%-20s%-20s%-10d%-10d", str, server_name,
		  seg_name, srv->api_client_index, srv->accept_cookie);
    }
  else
    s = format (s, "%-40s%-20s", str, server_name);
  return s;
}

u8 *
format_application_client (u8 * s, va_list * args)
{
  application_t *client = va_arg (*args, application_t *);
  int verbose = va_arg (*args, int);
  stream_session_t *session;
  u8 *str, *seg_name;
  u32 segment_size;

  if (client == 0)
    {
      if (verbose)
	s =
	  format (s, "%-40s%-20s%-10s", "Connection", "Segment",
		  "API Client");
      else
	s = format (s, "%-40s", "Connection");

      return s;
    }

  session = stream_session_get (client->session_index, client->thread_index);
  str = format (0, "%U", format_stream_session, session, verbose);

  session_manager_get_segment_info (session->server_segment_index, &seg_name,
				    &segment_size);
  if (verbose)
    {
      s = format (s, "%-40s%-20s%-10d%", str, seg_name,
		  client->api_client_index);
    }
  else
    s = format (s, "%-40s", str);
  return s;
}

static clib_error_t *
show_app_command_fn (vlib_main_t * vm, unformat_input_t * input,
		     vlib_cli_command_t * cmd)
{
  session_manager_main_t *smm = &session_manager_main;
  application_t *app;
  int do_server = 0;
  int do_client = 0;
  int verbose = 0;

  if (!smm->is_enabled)
    {
      clib_error_return (0, "session layer is not enabled");
    }

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "server"))
	do_server = 1;
      else if (unformat (input, "client"))
	do_client = 1;
      else if (unformat (input, "verbose"))
	verbose = 1;
      else
	break;
    }

  if (do_server)
    {
      if (pool_elts (app_pool))
	{
	  vlib_cli_output (vm, "%U", format_application_server,
			   0 /* header */ ,
			   verbose);
          /* *INDENT-OFF* */
          pool_foreach (app, app_pool,
          ({
            if (app->mode == APP_SERVER)
              vlib_cli_output (vm, "%U", format_application_server, app,
                               verbose);
          }));
          /* *INDENT-ON* */
	}
      else
	vlib_cli_output (vm, "No active server bindings");
    }

  if (do_client)
    {
      if (pool_elts (app_pool))
	{
	  vlib_cli_output (vm, "%U", format_application_client,
			   0 /* header */ ,
			   verbose);
          /* *INDENT-OFF* */
          pool_foreach (app, app_pool,
          ({
            if (app->mode == APP_CLIENT)
              vlib_cli_output (vm, "%U", format_application_client, app,
                               verbose);
          }));
          /* *INDENT-ON* */
	}
      else
	vlib_cli_output (vm, "No active client bindings");
    }

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_app_command, static) =
{
  .path = "show app",
  .short_help = "show app [server|client] [verbose]",
  .function = show_app_command_fn,
};
/* *INDENT-ON* */

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */