// calculator.c - a simple calculator

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

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

#include "calculator.h"

static Stack *calculator_stack_ = NULL;

//------------------------------------------------------------
// creates the stack, registers all handlers, sets the prompt
// and starts the input loop of the command-line dispatcher
int main(int argc,char *argv[])
{
  calculator_stack_ = newStack(standardErrorHandler);
  registerAllHandlers();
  setPrompt("calc> ",NULL,standardErrorHandler);
  runInputLoop(standardErrorHandler);
  deleteStack(calculator_stack_,standardErrorHandler);
  return(0);
}

//------------------------------------------------------------
// helper function that registers all command handlers with
// the dispatcher. If you write new operations, just add the
// appropriate line of code here for registration
void registerAllHandlers()
{
  setDefaultHandler(operand);
  addToDispatcher("+",NULL,addOperation,standardErrorHandler);
  addToDispatcher("-",NULL,subtractOperation,standardErrorHandler);
  addToDispatcher("*",NULL,multiplyOperation,standardErrorHandler);
  addToDispatcher("/",NULL,divideOperation,standardErrorHandler);
  addToDispatcher("p",NULL,popOperation,standardErrorHandler);
  addToDispatcher("pop",NULL,popOperation,standardErrorHandler);
  addToDispatcher("d",NULL,dupOperation,standardErrorHandler);
  addToDispatcher("dup",NULL,dupOperation,standardErrorHandler);
  addToDispatcher("s",NULL,swapOperation,standardErrorHandler);
  addToDispatcher("swap",NULL,swapOperation,standardErrorHandler);
  addToDispatcher("c",NULL,clearOperation,standardErrorHandler);
  addToDispatcher("clear",NULL,clearOperation,standardErrorHandler);
  addToDispatcher("q",NULL,quitOperation,standardErrorHandler);
  addToDispatcher("quit",NULL,quitOperation,standardErrorHandler);
}

//------------------------------------------------------------
// implementation of the add operation. Pops two operands
// from the stack, adds them and pushes the result on the stack
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   NOT_ENOUGH_OPERANDS
//                   OUT_OF_MEMORY
void addOperation(unsigned char* command,
                  unsigned char **params,
                  unsigned num_params)
{
  double *operand0 = NULL;
  double *operand1 = NULL;
  double *result = NULL;

  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in addOperation");
    return;
  }
  if (getNumElementsFromStack(calculator_stack_,
                              standardErrorHandler) < 2)
  {
    standardErrorHandler(NOT_ENOUGH_OPERANDS,"+");
    return;
  }
  operand0 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  operand1 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  result = (double*)malloc(sizeof(double));
  if (!result)
  {
    standardErrorHandler(OUT_OF_MEMORY,
                         "in addOperation");
    return;
  }
  *result = *operand1 + *operand0;
  free(operand0);
  free(operand1);
  push(calculator_stack_,result,
       defaultDestructor,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the subtract operation. Pops two operands
// from the stack, subtracts the first from the second and
// pushes the result on the stack
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   NOT_ENOUGH_OPERANDS
//                   OUT_OF_MEMORY
void subtractOperation(unsigned char* command,
                       unsigned char **params,
                       unsigned num_params)
{
  double *operand0 = NULL;
  double *operand1 = NULL;
  double *result = NULL;

  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in subtractOperation");
    return;
  }

  if (getNumElementsFromStack(calculator_stack_,
                              standardErrorHandler) < 2)
  {
    standardErrorHandler(NOT_ENOUGH_OPERANDS,"-");
    return;
  }
  operand0 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  operand1 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  result = (double*)malloc(sizeof(double));
  if (!result)
  {
    standardErrorHandler(OUT_OF_MEMORY,
                         "in subtractOperation");
    return;
  }
  *result = *operand1 - *operand0;
  free(operand0);
  free(operand1);
  push(calculator_stack_,result,
       defaultDestructor,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the multiply operation. Pops two operands
// from the stack, multiplies them and pushes the result on
// the stack
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   NOT_ENOUGH_OPERANDS
//                   OUT_OF_MEMORY
void multiplyOperation(unsigned char* command,
                       unsigned char **params,
                       unsigned num_params)
{
  double *operand0 = NULL;
  double *operand1 = NULL;
  double *result = NULL;

  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in multiplyOperation");
    return;
  }

  if (getNumElementsFromStack(calculator_stack_,
                              standardErrorHandler) < 2)
  {
    standardErrorHandler(NOT_ENOUGH_OPERANDS,"*");
    return;
  }
  operand0 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  operand1 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  result = (double*)malloc(sizeof(double));
  if (!result)
  {
    standardErrorHandler(OUT_OF_MEMORY,
                         "in multiplyOperation");
    return;
  }
  *result = *operand1 * *operand0;
  free(operand0);
  free(operand1);
  push(calculator_stack_,result,
       defaultDestructor,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the divide operation. Pops two operands
// from the stack, divides the second by the first and pushes
// the result on the stack
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   NOT_ENOUGH_OPERANDS
//                   OUT_OF_MEMORY
void divideOperation(unsigned char* command,
                     unsigned char **params,
                     unsigned num_params)
{
  double *operand0 = NULL;
  double *operand1 = NULL;
  double *result = NULL;

  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in divideOperation");
    return;
  }

  if (getNumElementsFromStack(calculator_stack_,
                              standardErrorHandler) < 2)
  {
    standardErrorHandler(NOT_ENOUGH_OPERANDS,"/");
    return;
  }
  operand0 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  operand1 = (double*)pop(calculator_stack_,
                          standardErrorHandler);
  result = (double*)malloc(sizeof(double));
  if (!result)
  {
    standardErrorHandler(OUT_OF_MEMORY,
                         "in divideOperation");
    return;
  }
  *result = *operand1 / *operand0;
  free(operand0);
  free(operand1);
  push(calculator_stack_,result,
       defaultDestructor,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the pop operation. Pops one operand
// from the stack
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   NOT_ENOUGH_OPERANDS
void popOperation(unsigned char* command,
                  unsigned char **params,
                  unsigned num_params)
{
  double *operand = NULL;

  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in popOperation");
    return;
  }

  if (getNumElementsFromStack(calculator_stack_,
                              standardErrorHandler) < 1)
  {
    standardErrorHandler(NOT_ENOUGH_OPERANDS,"pop");
    return;
  }
  operand = (double*)pop(calculator_stack_,
                         standardErrorHandler);
  free(operand);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the dup operation. Makes a duplicate of
// the current operand on the stack and pushes the the result
// on the stack
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   NOT_ENOUGH_OPERANDS
//                   OUT_OF_MEMORY
void dupOperation(unsigned char* command,
                  unsigned char **params,
                  unsigned num_params)
{
  double *operand = NULL;
  double *result = NULL;

  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in dupOperation");
    return;
  }

  if (getNumElementsFromStack(calculator_stack_,
                              standardErrorHandler) < 1)
  {
    standardErrorHandler(NOT_ENOUGH_OPERANDS,"dup");
    return;
  }
  operand = (double*)peek(calculator_stack_,0,
                          standardErrorHandler);
  result = (double*)malloc(sizeof(double));
  if (!result)
  {
    standardErrorHandler(OUT_OF_MEMORY,
                         "in dupOperation");
    return;
  }
  *result = *operand;
  push(calculator_stack_,result,
       defaultDestructor,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the swap operation. Just calls the stack's
// swap access function.
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   NOT_ENOUGH_OPERANDS
void swapOperation(unsigned char* command,
                   unsigned char **params,
                   unsigned num_params)
{
  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in swapOperation");
    return;
  }

  if (getNumElementsFromStack(calculator_stack_,
                              standardErrorHandler) < 2)
  {
    standardErrorHandler(NOT_ENOUGH_OPERANDS,"swap");
    return;
  }
  swap(calculator_stack_,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the clear operation. Clears the whole stack
//
// triggered errors: COMMAND_NOT_REGISTERED
void clearOperation(unsigned char* command,
                    unsigned char **params,
                    unsigned num_params)
{
  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,
                         "in clearOperation");
    return;
  }
  clear(calculator_stack_,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// implementation of the quit operation. Stops the input loop
// of the dispatcher.
void quitOperation(unsigned char* command,
                   unsigned char **params,
                   unsigned num_params)
{
  stopInputLoop(0);
}

//------------------------------------------------------------
// default handler for all input that does not
// match a recognized command. In this case it can only be
// an operand (or an error). Parses the input and makes
// an operand (of type double) out of it. Pushes the result
// on the stack.
//
// triggered errors: COMMAND_NOT_REGISTERED
//                   OUT_OF_MEMORY
void operand(unsigned char* command,unsigned char **params,
             unsigned num_params)
{
  double *operand = NULL;
  double digit;
  unsigned char is_negative = FALSE;
  double digit_multiplier = 1.0;

  if (num_params)
  {
    standardErrorHandler(COMMAND_NOT_REGISTERED,command);
    return;
  }
  if (!strlen(command))
    return;

  operand = malloc(sizeof(double));
  if (!operand)
  {
    standardErrorHandler(OUT_OF_MEMORY,"operand");
    return;
  }
  *operand = 0.0;
  if (*command == '-')
  {
    command++;
    is_negative = TRUE;
  }
  if (*command == '.')
  {
    command++;
    digit_multiplier = 0.1;
  }
  while(*command)
  {
    if (*command == '.')
    {
      if (digit_multiplier < 1.0) // there was already a comma
      {
        standardErrorHandler(COMMAND_NOT_REGISTERED,command);
        return;
      }
      digit_multiplier = 0.1;
      command++;
      continue;
    }
    if ((*command < '0') || (*command > '9'))
    {
      standardErrorHandler(COMMAND_NOT_REGISTERED,command);
      return;
    }
    digit = (double)(*command - '0');
    if (digit_multiplier < 1.0)
    {
      digit *= digit_multiplier;
      digit_multiplier /= 10.0;
    }
    else
      *operand *= 10.0;
    command++;
    *operand += digit;
  }
  if (is_negative)
    *operand *= -1.0;
  push(calculator_stack_,operand,
       defaultDestructor,standardErrorHandler);
  showStack(calculator_stack_);
}

//------------------------------------------------------------
// standard error handler for all errors that are
// triggered by the error-callback mechanism.
void standardErrorHandler(int error_type,void *special)
{
  switch(error_type)
  {
  case INTERNAL_ERROR:
    fprintf(stderr,"Error: internal error... %s\n",
            (char*)special);
    break;
  case OUT_OF_MEMORY:
    fprintf(stderr,"Error: out of memory... %s\n",
            (char*)special);
    break;
  case ILLEGAL_ARGUMENTS:
    fprintf(stderr,"Error: illegal arguments... %s\n",
            (char*)special);
    break;
  case ILLEGAL_TYPE:
    fprintf(stderr,"Error: illegal type... %s\n",
            (char*)special);
    break;
        // the following is the same as COMMAND_NOT_REGISTERED
  case ELEMENT_NOT_EXISTING:
    fprintf(stderr,"Error: unknown command - %s\n",
            (char*)special);
    return; // non-critical error - don't exit
  case DUPLICATE_KEY:
    fprintf(stderr,"Error: command registered twice - %s\n",
            (char*)special);
    return; // non-critical error - don't exit
  case NOT_ENOUGH_OPERANDS:
    fprintf(stderr,"Error: not enough operands in %s\n",
            (char*)special);
    return; // non-critical error - don't exit
  default:
    fprintf(stderr,"Error: %d (unknown type)\n",error_type);
  }
  exit(error_type);
}

//------------------------------------------------------------
// default destructor for operands pushed on the stack. All
// operands are pointers to double, so a single free operation
// is enough.
void defaultDestructor(void *element,
                       void (*error_handler)(int error_type,
                                             void *special))
{
  free(element);
}

//------------------------------------------------------------
// function that shows the stack in the desired output format
void showStack(Stack *stack)
{
  unsigned count = getNumElementsFromStack(
    stack,standardErrorHandler);
  printf("==============================\n");
  while(count--)
    printf("%f\n",*(double*)peek(stack,count,standardErrorHandler));
  printf("==============================\n");
}
