// advanced_input_handling.c - useful input handling using
//                             callbacks and a dispatcher

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "default_debug.h"
#include "default_error_handling.h"
#include "hashmap.h"
#include "std_hash_functions.h"
#include "advanced_input_handling.h"
#include "advanced_input_handling_private.h"

static unsigned char *prompt_ = NULL;
static void (*prompt_destructor_)(void *prompt,
                                 void (*error_handler)(
                                   int error_type,
                                   void *special)) = NULL;
static Hashmap *registered_commands_ = NULL;
static unsigned char stop_loop_ = 0;
static void (*default_handler_)(unsigned char *command,
                                unsigned char **params,
                                unsigned num_params) = NULL;
static void (*error_handler_)(int error_type,
                              void *special) = NULL;

//------------------------------------------------------------
// main run-loop of the program that accepts keyboard input
void runInputLoop(void (*error_handler)(int error_type,
                                        void *special))
{
  unsigned char *input_line = malloc(sizeof(unsigned char) * 
                                     (MAX_LINE_LENGTH + 1));
  if (error_handler)
    error_handler_ = error_handler;
  else
    error_handler_ = defaultErrorHandler;
  stop_loop_ = 0;
  while(!stop_loop_)
  {
    if (!prompt_)
      printf(">");
    else
      printf("%s",prompt_);
    readLine(input_line,MAX_LINE_LENGTH,error_handler);
    dispatch(input_line);
  }
}

//------------------------------------------------------------
// stops the input loop after the current (or next, if there
// isn't any current) handler has finished its work.
void stopInputLoop()
{
  stop_loop_ = 1;
}

//------------------------------------------------------------
// reads one line from the keyboard
int readLine(unsigned char *str,unsigned max_length,
             void (*error_handler)(int error_type,
                                   void *special))
{
  char character_read = '\0';
  unsigned num_chars = 0;

  if (!error_handler)
    error_handler = defaultErrorHandler;

  if ((max_length <= 1) || (!str))
  {
    error_handler(ILLEGAL_ARGUMENTS,"readLine");
    return(-1);
  }

  while((character_read = getchar()) != '\n')
  {
    *str++ = character_read;
    if (++num_chars >= max_length)
      break;
  }
  *str = '\0';
  return(num_chars);
}

//------------------------------------------------------------
// registers a command and corresponding handler function
// with the dispatcher
void addToDispatcher(unsigned char *command,
                     void (*command_destructor)(
                       void *command_str,
                       void (*error_handler)(int error_type,
                                             void *special)),
                     void (*handler)(
                       unsigned char *command,
                       unsigned char **params,
                       unsigned num_params),
                     void (*error_handler)(int error_type,
                                           void *special))
{
  if (!error_handler)
    error_handler = defaultErrorHandler;

  if ((!command) || (!handler))
  {
    error_handler(ILLEGAL_ARGUMENTS,"addToDispatcher");
    return;
  }

  if (!registered_commands_)
    registered_commands_ = newHashmap(HASH_MODULUS,
                                      stringHash,
                                      stringCompare,
                                      error_handler);
  insertIntoHashmap(registered_commands_,
                    command,
                    handler,
                    command_destructor,
                    NULL,
                    error_handler);
}

//------------------------------------------------------------
// registers a default handler with the dispatcher. The
// default handler is called every time when no handler for
// the given command is found.
void setDefaultHandler(void (*handler)(unsigned char *command,
                                       unsigned char **params,
                                       unsigned num_params))
{
  default_handler_ = handler;
}

//------------------------------------------------------------
// unregisters a command from the dispatcher
void removeFromDispatcher(unsigned char *command,
                          void (*error_handler)(int error_type,
                                                void *special))
{
  if (!error_handler)
    error_handler = defaultErrorHandler;

  if (!registered_commands_)
  {
    error_handler(COMMAND_NOT_REGISTERED,command);
    return;
  }
  deleteFromHashmap(registered_commands_,
                    command,
                    error_handler);
}

//------------------------------------------------------------
// sets the prompt that is displayed when the input loop
// waits for a user-input
void setPrompt(unsigned char *prompt,
               void (*destructor)(void *prompt,
                                  void (*error_handler)(
                                    int error_type,
                                    void *special)),
               void (*error_handler)(int error_type,
                                     void *special))
{
  if ((prompt_) && (prompt_destructor_))
    prompt_destructor_(prompt_,error_handler);
  prompt_ = prompt;
  prompt_destructor_ = destructor;
}

//------------------------------------------------------------
// dispatches the keyboard input
void dispatch(unsigned char *input_line)
{
  void (*handler)(unsigned char *command,
                  unsigned char **params,
                  unsigned num_params) = NULL;
  unsigned char *command = NULL;
  unsigned char **params = NULL;
  unsigned char **help = NULL;
  unsigned num_params = 0;

  num_params = splitCommandLine(input_line,&command,&params);
  if (registered_commands_)
    handler = getFromHashmap(registered_commands_,
                             command,
                             ignoreNotRegisteredErrorHandler);
  if (handler) // NULL happens if command not registered
    handler(command,params,num_params);
  else
  {
    if (default_handler_)
      default_handler_(command,params,num_params);
    else
      error_handler_(COMMAND_NOT_REGISTERED,input_line);
  }
  free(command);
  command = NULL;
  help = params;
  while(num_params--)
    free(*help++);
  help = NULL;
  free(params);
  params = NULL;
}

//------------------------------------------------------------
// splits the command line into command (=first word) and
// parameters. The delimiter for separation is a space-char
unsigned splitCommandLine(unsigned char *input_line,
                          unsigned char **command,
                          unsigned char ***params)
{
  unsigned char *command_end_pos = NULL;
  unsigned char *current = input_line;
  unsigned length = 0;
  unsigned num_params = 0;

      // remove leading space
  while(*current && (*current == ' '))
    current++;

  command_end_pos = strchr(current,' ');

  if (!command_end_pos) // no space found
  {
    *command = (unsigned char*)malloc(
      strlen(current) * sizeof(unsigned char) + 1);
    strcpy(*command,current);
    return(0);
  }
  length = command_end_pos - current;
  *command = (unsigned char*)malloc(
    length * sizeof(unsigned char) + 1);
  strncpy(*command,current,length);
  (*command)[length] = '\0';

  *params = NULL; // make sure nothing bad happens with realloc
  for(;;)
  {
    current = command_end_pos + 1;
        // remove leading spaces
    while(*current && (*current == ' '))
      current++;

    command_end_pos = strchr(current,' ');

    if (!command_end_pos) // no space found
    {
      length = strlen(current);
          // could have been trailing spaces (removed
          // above) and therefore no more parameter exists
      if (!length)
        return(num_params);
      *params = (unsigned char**)realloc(
        *params,num_params * sizeof(unsigned char*) + 1);
      (*params)[num_params] = (unsigned char*)malloc(
        length * sizeof(unsigned char) + 1);
      strcpy((*params)[num_params],current);
      return(++num_params);
    }
    length = command_end_pos - current;
    *params = (unsigned char**)realloc(
      *params,num_params * sizeof(unsigned char*) + 1);
    (*params)[num_params] = (unsigned char*)malloc(
      length * sizeof(unsigned char) + 1);
    strncpy((*params)[num_params],current,length);
    (*params)[num_params++][length] = '\0';
  }
      // no return necessary, because control-flow can never
      // come here.
}

//------------------------------------------------------------
// special error handler that ignores the error callback which
// would happen in case a command is not registered. This handler
// is used when a default handler is set. In this case all
// input that is not recognized is passed on to the default
// input handler
void ignoreNotRegisteredErrorHandler(int error_type,
                                     void *special)
{
  if (error_type != COMMAND_NOT_REGISTERED)
    error_handler_(error_type,special);
}
