/********************************************************************
 * Berry module `webserver`
 * 
 * To use: `import webserver`
 * 
 * Allows to respond to HTTP request
 *******************************************************************/
#include "be_constobj.h"

#ifdef USE_WEBSERVER

#include "be_exec.h"
#include "be_vm.h"

extern int w_webserver_member(bvm *vm);
extern int w_webserver_on(bvm *vm);
extern int w_webserver_state(bvm *vm);

extern int w_webserver_check_privileged_access(bvm *vm);
extern int w_webserver_redirect(bvm *vm);
extern int w_webserver_content_start(bvm *vm);
extern int w_webserver_content_open(bvm *vm);
extern int w_webserver_content_send(bvm *vm);
extern int w_webserver_content_response(bvm *vm);
extern int w_webserver_content_send_style(bvm *vm);
extern int w_webserver_content_flush(bvm *vm);
extern int w_webserver_content_stop(bvm *vm);
extern int w_webserver_content_close(bvm *vm);
extern int w_webserver_content_button(bvm *vm);

extern int w_webserver_html_escape(bvm *vm);

extern int w_webserver_argsize(bvm *vm);
extern int w_webserver_arg(bvm *vm);
extern int w_webserver_arg_name(bvm *vm);
extern int w_webserver_has_arg(bvm *vm);

extern int w_webserver_header(bvm *vm);

// To allow a full restart of the Berry VM, we need to supplement the webserver Request Handler
// model from Arduino framework.
// We use our own list of callbacks

#define WEBSERVER_REQ_HANDLER_HOOK_MAX       16      // max number of callbacks, each callback requires a distinct address
typedef struct be_webserver_callback_hook_t {
  bvm *vm;                // make sure we are using the same VM
  bvalue f;               // the Berry function to call
} be_webserver_callback_hook_t;

static be_webserver_callback_hook_t be_webserver_cb_hooks[WEBSERVER_REQ_HANDLER_HOOK_MAX];

static void be_call_webserver_hook_cb(int32_t num);
typedef void (*berry_webserver_cb_t)(void);
#define WEBSERVER_HOOK_CB(n) void berry_webserver_cb_##n(void) { be_call_webserver_hook_cb(n); }
// list the callbacks
WEBSERVER_HOOK_CB(0);
WEBSERVER_HOOK_CB(1);
WEBSERVER_HOOK_CB(2);
WEBSERVER_HOOK_CB(3);
WEBSERVER_HOOK_CB(4);
WEBSERVER_HOOK_CB(5);
WEBSERVER_HOOK_CB(6);
WEBSERVER_HOOK_CB(7);
WEBSERVER_HOOK_CB(8);
WEBSERVER_HOOK_CB(9);
WEBSERVER_HOOK_CB(10);
WEBSERVER_HOOK_CB(11);
WEBSERVER_HOOK_CB(12);
WEBSERVER_HOOK_CB(13);
WEBSERVER_HOOK_CB(14);
WEBSERVER_HOOK_CB(15);

// array of callbacks
static const berry_webserver_cb_t berry_callback_array[WEBSERVER_REQ_HANDLER_HOOK_MAX] = {
  berry_webserver_cb_0,
  berry_webserver_cb_1,
  berry_webserver_cb_2,
  berry_webserver_cb_3,
  berry_webserver_cb_4,
  berry_webserver_cb_5,
  berry_webserver_cb_6,
  berry_webserver_cb_7,
  berry_webserver_cb_8,
  berry_webserver_cb_9,
  berry_webserver_cb_10,
  berry_webserver_cb_11,
  berry_webserver_cb_12,
  berry_webserver_cb_13,
  berry_webserver_cb_14,
  berry_webserver_cb_15,
};

// Return slot number
// -1 if no more available
berry_webserver_cb_t be_webserver_allocate_hook(bvm *vm, int32_t slot, bvalue *f) {
  if (slot < 0 || slot >= WEBSERVER_REQ_HANDLER_HOOK_MAX) return NULL;   // invalid call, avoid a crash
  be_webserver_cb_hooks[slot].vm = vm;
  be_webserver_cb_hooks[slot].f = *f;
  return berry_callback_array[slot];
}

/*********************************************************************************************\
 * `be_webserver_cb_deinit`:
 *  Clean any callback for this VM
\*********************************************************************************************/
void be_webserver_cb_deinit(bvm *vm) {
  for (int32_t i = 0; i < WEBSERVER_REQ_HANDLER_HOOK_MAX; i++) {
    if (be_webserver_cb_hooks[i].vm == vm) {
      be_webserver_cb_hooks[i].vm = NULL;
    }
  }
}

/*********************************************************************************************\
 * Callback structures
 * 
\*********************************************************************************************/
void be_call_webserver_hook_cb(int32_t num) {
  // call berry cb dispatcher
  int32_t ret = 0;
  // retrieve vm and function
  if (num < 0 || num >= WEBSERVER_REQ_HANDLER_HOOK_MAX || be_webserver_cb_hooks[num].vm == NULL) return;   // invalid call, avoid a crash

  bvm * vm = be_webserver_cb_hooks[num].vm;
  bvalue *f = &be_webserver_cb_hooks[num].f;

  // push function (don't check type)
  bvalue *top = be_incrtop(vm);
  *top = *f;

  ret = be_pcall(vm, 0);   // 4 arguments
  if (ret != 0) {
    if (vm->obshook != NULL) (*vm->obshook)(vm, BE_OBS_PCALL_ERROR);
    be_pop(vm, be_top(vm));       // clear Berry stack
    return;
  }
  be_pop(vm, 1);    // remove result
  return;
}

/* @const_object_info_begin
module webserver (scope: global) {
    member, func(w_webserver_member)

    on, func(w_webserver_on)
    state, func(w_webserver_state)

    check_privileged_access, func(w_webserver_check_privileged_access)
    redirect, func(w_webserver_redirect)
    content_send, func(w_webserver_content_send)
    content_response, func(w_webserver_content_response)
    content_send_style, func(w_webserver_content_send_style)
    content_flush, func(w_webserver_content_flush)
    content_open, func(w_webserver_content_open)
    content_start, func(w_webserver_content_start)
    content_stop, func(w_webserver_content_stop)
    content_close, func(w_webserver_content_close)
    content_button, func(w_webserver_content_button)

    html_escape, func(w_webserver_html_escape)

    arg_size, func(w_webserver_argsize)
    arg, func(w_webserver_arg)
    arg_name, func(w_webserver_arg_name)
    has_arg, func(w_webserver_has_arg)

    header, func(w_webserver_header)
}
@const_object_info_end */
#include "be_fixed_webserver.h"

#endif // USE_WEBSERVER
