// work_list.c - example implementation of a list that
//               manages stuff that we have to do

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

// function declarations

void insertTask(unsigned char *task,unsigned priority);
void showNextTasks(unsigned char *input_line);
void showAllTasks(unsigned char *input_line);
void deleteTask(unsigned id);
void changePriority(unsigned id,unsigned priority);
void quitProgram(unsigned char *input_line);
void run();
int readLine(unsigned char *str,unsigned max_length);
void addToDispatcher(unsigned char input,
                     void (*handler)(unsigned char *input_line));
void removeFromDispatcher(unsigned char input);
void dispatch(unsigned char *input_line);

void callInsertTask(unsigned char *input_line);
void callDeleteTask(unsigned char *input_line);
void callChangePriority(unsigned char *input_line);
void taskWithGivenIdNotFound(unsigned id);
void commandNotRecognized(unsigned char *command_line);
void exitOnFatalErrorOutOfMemory();
void exitOnFatalErrorIllegalArgument(unsigned char *func_name);
unsigned char *cloneString(unsigned char *str);

// some constants

#define MAX_LINE_LENGTH 512
#define ILLEGAL_ARGUMENTS -1
#define FALSE 0
#define TRUE  1

// structure needed for our list and access functions for it

struct ListNode
{
  unsigned char *task;
  unsigned priority;
  unsigned id;
  struct ListNode *next;
};

struct ListNode *newListNode(unsigned char *task,
                             unsigned priority);
void deleteListNode(struct ListNode *node);
void setNextInListNode(struct ListNode *node,
                       struct ListNode *next);
struct ListNode *getNextFromListNode(struct ListNode *node);
void setPriorityInListNode(struct ListNode *node,
                           unsigned priority);
unsigned getPriorityFromListNode(struct ListNode *node);
unsigned getIdFromListNode(struct ListNode *node);
unsigned char *getTaskFromListNode(struct ListNode *node);

// our first node of the list

struct ListNode *first_node_ = NULL;
unsigned current_id_ = 0;
void (*handler_[256])(unsigned char *input_line);

//------------------------------------------------------------
int main()
{
  addToDispatcher('i',callInsertTask);
  addToDispatcher('n',showNextTasks);
  addToDispatcher('a',showAllTasks);
  addToDispatcher('d',callDeleteTask);
  addToDispatcher('c',callChangePriority);
  addToDispatcher('q',quitProgram);
  run();
  return(0);
}

//------------------------------------------------------------
// creates a new node and inserts it in the correct position
// according to the given priority
void insertTask(unsigned char *task,unsigned priority)
{
  struct ListNode *node = newListNode(task,priority);
  struct ListNode *current = NULL;
  struct ListNode *previous = NULL;

  if (!first_node_) // this is the first node in the list
  {
    first_node_ = node;
    return;
  }

  // find the correct position
  current = first_node_;
  while(current && (getPriorityFromListNode(current) < priority))
  {
    previous = current;
    current = getNextFromListNode(current);
  }
  if (!previous) // first node
    first_node_ = node;
  else
    setNextInListNode(previous,node);
  setNextInListNode(node,current);
}


//------------------------------------------------------------
void showNextTasks(unsigned char *input_line)
{
  struct ListNode *current = first_node_;
  unsigned highest_priority;

  if (!current)
    return;
  highest_priority = getPriorityFromListNode(current);

  while(current && 
        (getPriorityFromListNode(current) <= highest_priority))
  {
    printf("%d: Priority %d, %s\n",getIdFromListNode(current),
                                   highest_priority,
                                   getTaskFromListNode(current));
    current = getNextFromListNode(current);
  }
}


//------------------------------------------------------------
void showAllTasks(unsigned char *input_line)
{
  struct ListNode *current = first_node_;
  while(current)
  {
    printf("%d: Priority %d, %s\n",getIdFromListNode(current),
                                   getPriorityFromListNode(current),
                                   getTaskFromListNode(current));
    current = getNextFromListNode(current);
  }
}


//------------------------------------------------------------
void deleteTask(unsigned id)
{
  struct ListNode *current = first_node_;
  struct ListNode *previous = NULL;

  while(current && (getIdFromListNode(current) != id))
  {
    previous = current;
    current = getNextFromListNode(current);
  }
  if (!current) // id not found
  {
    taskWithGivenIdNotFound(id);
    return;
  }
  if (!previous) // first node is deleted
    first_node_ = getNextFromListNode(current);
  else
    setNextInListNode(previous,getNextFromListNode(current));
  deleteListNode(current);
}


//------------------------------------------------------------
void changePriority(unsigned id,unsigned priority)
{
  short found_insertion_point = FALSE;
  struct ListNode *insert_after = NULL;
  struct ListNode *current = first_node_;
  struct ListNode *previous = NULL;
  struct ListNode *node_to_change_priority = NULL;
  struct ListNode *predecessor_of_node = NULL;
  
  while(current && 
        (!(node_to_change_priority && found_insertion_point)))
  {
    if ((!found_insertion_point) && 
        (priority < getPriorityFromListNode(current)))
    {
      found_insertion_point = TRUE;
      insert_after = previous;
    }
    if (id == getIdFromListNode(current))
    {
      node_to_change_priority = current;
      predecessor_of_node = previous;
      if (getPriorityFromListNode(current) == priority)
        return;     // nothing to change
    }
    previous = current;
    current = getNextFromListNode(current);
  }
  if (!found_insertion_point) // insert after last node
    insert_after = previous;
  if (!node_to_change_priority) // id not found
  {
    taskWithGivenIdNotFound(id);
    return;
  }
  setPriorityInListNode(node_to_change_priority,priority);
  // be careful: a node can't be inserted after itself, because
  // this would kill the list (and there is nothing to be done
  // anyway)
  if (node_to_change_priority == insert_after)
    return;
  // step 1: remove node from list
  if (!predecessor_of_node) // was the first node in the list
    first_node_ = getNextFromListNode(node_to_change_priority);
  else
    setNextInListNode(predecessor_of_node,
                      getNextFromListNode(node_to_change_priority));
  // step 2: insert at new location
  if (!insert_after) // insert before first element
  {
    setNextInListNode(node_to_change_priority,first_node_);
    first_node_ = node_to_change_priority;
    return;
  }
  setNextInListNode(node_to_change_priority,
                    getNextFromListNode(insert_after));
  setNextInListNode(insert_after,node_to_change_priority);
}


//------------------------------------------------------------
void quitProgram(unsigned char *input_line)
{
  exit(0);
}

//============================================================
// all ListNode access functions

//------------------------------------------------------------
struct ListNode *newListNode(unsigned char *task,
                             unsigned priority)
{
  struct ListNode *node = malloc(sizeof(struct ListNode));
  if (!node)
    exitOnFatalErrorOutOfMemory();
  node->task = cloneString(task);
  node->priority = priority;
  node->id = current_id_++;
  node->next = NULL;
  return(node);
}


//------------------------------------------------------------
void deleteListNode(struct ListNode *node)
{
  if (!node)
    exitOnFatalErrorIllegalArgument("deleteListNode");
  if (node->task)
    free(node->task);
  free(node);
  return;
}

//------------------------------------------------------------
void setNextInListNode(struct ListNode *node,
                       struct ListNode *next)
{
  if (!node)
    exitOnFatalErrorIllegalArgument("setNextInListNode");
  node->next = next;
}


//------------------------------------------------------------
struct ListNode *getNextFromListNode(struct ListNode *node)
{
  if (!node)
    exitOnFatalErrorIllegalArgument("getNextFromListNode");
  return(node->next);
}


//------------------------------------------------------------
void setPriorityInListNode(struct ListNode *node,
                           unsigned priority)
{
  if (!node)
    exitOnFatalErrorIllegalArgument("setPriorityInListNode");
  node->priority = priority;
}


//------------------------------------------------------------
unsigned getPriorityFromListNode(struct ListNode *node)
{
  if (!node)
    exitOnFatalErrorIllegalArgument("getPriorityFromListNode");
  return(node->priority);
}

//------------------------------------------------------------
unsigned getIdFromListNode(struct ListNode *node)
{
  if (!node)
    exitOnFatalErrorIllegalArgument("getIdFromListNode");
  return(node->id);
}

//------------------------------------------------------------
unsigned char *getTaskFromListNode(struct ListNode *node)
{
  if (!node)
    exitOnFatalErrorIllegalArgument("getTaskFromListNode");
  return(node->task);
}

//------------------------------------------------------------
// The main run-loop that accepts the keyboard input

void run()
{
  unsigned char *input_line = malloc(sizeof(unsigned char) * 
                                     (MAX_LINE_LENGTH + 1));
  int chars_read = 0;

  for(;;)
  {
    printf("Worklist> ");
    if ((chars_read = readLine(input_line,MAX_LINE_LENGTH) == 
         ILLEGAL_ARGUMENTS))
    {
      printf("Error: readLine returned ILLEGAL_ARGUMENTS\n");
      exit(-1);
    }
    dispatch(input_line);
  }
}

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

  if ((max_length <= 1) || (str == NULL))
    return(ILLEGAL_ARGUMENTS);

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


//============================================================
// keyboard event dispatcher functions

//------------------------------------------------------------
void addToDispatcher(unsigned char input,
                     void (*handler)(unsigned char *input_line))
{
  handler_[input] = handler;
}

//------------------------------------------------------------
void removeFromDispatcher(unsigned char input)
{
  handler_[input] = NULL;
}

//------------------------------------------------------------
// dispatches the keyboard input

void dispatch(unsigned char *input_line)
{
  if (handler_[*input_line] != NULL)
    handler_[*input_line](input_line);
  else
    commandNotRecognized(input_line);
}

//------------------------------------------------------------
void callInsertTask(unsigned char *input_line)
{
  // for demonstration purposes we do everything by hand here
  unsigned char *help = input_line;
  unsigned priority = 0;

  while(*++help) // look for the start of the number
  {
    if ((*help >= '0') && (*help <= '9'))
      break;
    if (*help != ' ') // nothing else than spaces allowed here
    {
      commandNotRecognized(input_line);
      return;
    }
  }
  while((*help >= '0') && (*help <= '9')) // extract the number
    priority = (priority * 10) + (*help++ - '0');
  if (*help != ' ') // there must be a space after the number
  {
    commandNotRecognized(input_line);
    return;
  }
  while(*help++ == ' ') // skip additional spaces
    ;
  // the remainder of the line is the task description
  insertTask(--help,priority);
}


//------------------------------------------------------------
void callDeleteTask(unsigned char *input_line)
{
  // for demonstration purposes we do everything by hand here
  unsigned char *help = input_line;
  unsigned id = 0;

  while(*++help) // look for the start of the number
  {
    if ((*help >= '0') && (*help <= '9'))
      break;
    if (*help != ' ') // nothing else than spaces allowed here
    {
      commandNotRecognized(input_line);
      return;
    }
  }
  while((*help >= '0') && (*help <= '9')) // extract the number
    id = (id * 10) + (*help++ - '0');
  // here we should check, whether something unallowed follows,
  // I leave this up to you
  deleteTask(id);
}


//------------------------------------------------------------
void callChangePriority(unsigned char *input_line)
{
  // for demonstration purposes we do everything by hand here
  unsigned char *help = input_line;
  unsigned id = 0;
  unsigned priority = 0;

  while(*++help) // look for the start of the id
  {
    if ((*help >= '0') && (*help <= '9'))
      break;
    if (*help != ' ') // nothing else than spaces allowed here
    {
      commandNotRecognized(input_line);
      return;
    }
  }
  while((*help >= '0') && (*help <= '9')) // extract the number
    id = (id * 10) + (*help++ - '0');

  while(*++help) // look for the start of the priority
  {
    if ((*help >= '0') && (*help <= '9'))
      break;
    if (*help != ' ') // nothing else than spaces allowed here
    {
      commandNotRecognized(input_line);
      return;
    }
  }
  while((*help >= '0') && (*help <= '9')) // extract the number
    priority = (priority * 10) + (*help++ - '0');
  changePriority(id,priority);
}

//------------------------------------------------------------
void taskWithGivenIdNotFound(unsigned id)
{
  printf("Error: task with id %d does not exist\n",id);
}

//------------------------------------------------------------
void commandNotRecognized(unsigned char *command_line)
{
  printf("Error: command not recognized... %s\n",command_line);
}

//------------------------------------------------------------
// helper function if the program runs out of memory
void exitOnFatalErrorOutOfMemory()
{
  printf("Fatal Error: Out of Memory\n");
  exit(-1);
}

//------------------------------------------------------------
// helper function if a function gets an illegal argument
void exitOnFatalErrorIllegalArgument(unsigned char* func_name)
{
  printf("Fatal Error: IllegalArgument in function %s\n",func_name);
  exit(-2);
}

//------------------------------------------------------------
// This function returns a clone of the given string. This
// means that it also allocates the memory and returns the
// dynamically allocated  block. Therefore the clone has to be
// freed if no longer needed!
unsigned char *cloneString(unsigned char* str)
{
  unsigned char* ret = NULL;
  if (str)
  {
    if (!(ret = malloc(sizeof(unsigned char) * (strlen(str) + 1))))
      exitOnFatalErrorOutOfMemory();
    strcpy(ret,str);
  }
  return(ret);
}
