#include "MainLoop.h"
#include "Thread.h"
#include "LinkedList.h"
#include "mm_mark_and_sweep.h"
#include "mm.h"
#include "utils.h"

/*
Copyright (c) 2013-2016 Gustav Hartvigsson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */

/*
 * The default mainloop, it is hidden.
 */
SMainLoop *
_default_main_loop = NULL;

/*
 * The items that are held by the linked lists in SMainLoop.
 */
typedef struct SMainLoopItem {
  SMainLoopState state; /* in what list should this item be? */
  sboolean is_alive; /* If this is TRUE then this can be removed. */

  RunFunc callback; /* The callback that is run. */
  spointer user_data; /* Pointer to the user data that was provided. */
} SMainLoopItem;

typedef struct  SMainLoopTeardownHandler {
  Callback teardown_handler;

  spointer obj_self;
  spointer user_data;

} SMainLoopTeardownHandler;

/* This is the freeing function for MainLoopTeardownHandler objcets it also
 * calls the teardown handler.
 *
 * This reduces the iteration time for the teardown.
 */
void
_s_main_loop_teardown_handerer_obj_free (SMainLoopTeardownHandler * self);

struct SMainLoop {
  suint ref_count;
  SMainLoopState ring_1_state;
  SMainLoopState ring_2_state;
  sboolean is_run;

  SMutex * mutex[S_MAIN_LOOP_RING_LAST][S_MAIN_LOOP_STATE_LAST];

  SLinkedList * ring_1_event_handler_list;
  SLinkedList * ring_1_io_push_list;
  SLinkedList * ring_1_io_pull_list;
  SLinkedList * ring_1_workers_list;

  SLinkedList * ring_2_event_handler_list;
  SLinkedList * ring_2_io_push_list;
  SLinkedList * ring_2_io_pull_list;
  SLinkedList * ring_2_workers_list;

  SLinkedList * teardown_handler_list;

  SMutex * teardown_handler_mutex;
};

schar *
s_main_loop_state_get_name (SMainLoopState state) {
  return SMainLoopStateName[state];
}

schar *
s_main_loop_ring_get_name (SMainLoopRing ring) {
  return SMainLoopStateName[ring];
}

/* ************************************************************************** */

void
_s_main_loop_do_list (SLinkedList * event_handler_list);

void
_s_main_loop_do_ring_two (SMainLoop * self);

/* -----------------------------------------------------------------------------
 * imprementation of the real functions and stuffs.
 * -----------------------------------------------------------------------------
 */

SMainLoop *
s_main_loop_new () {
  SMainLoop * self = s_malloc (sizeof (SMainLoop));

  for (sint i = 0; i < S_MAIN_LOOP_RING_LAST; i++){
    for (sint j = 0; j < S_MAIN_LOOP_STATE_LAST; j++){
      self->mutex[i][j] = s_mutex_new ();
    }
  }

  self->teardown_handler_mutex = s_mutex_new ();

  s_mutex_lock (self->mutex[S_MAIN_LOOP_RING_NONE][S_MAIN_LOOP_STATE_NONE]);

  self->ref_count = 1;
  self->ring_1_state = S_MAIN_LOOP_STATE_EVENT;
  self->ring_2_state = S_MAIN_LOOP_STATE_EVENT;
  self->is_run = FALSE;

  self->ring_1_event_handler_list = s_linked_list_new (FREEFUNC (s_free));
  self->ring_1_io_push_list = s_linked_list_new (FREEFUNC (s_free));
  self->ring_1_io_pull_list = s_linked_list_new (FREEFUNC (s_free));
  self->ring_1_workers_list = s_linked_list_new (FREEFUNC (s_free));

  self->ring_2_event_handler_list = s_linked_list_new (FREEFUNC (s_free));
  self->ring_2_io_push_list = s_linked_list_new (FREEFUNC (s_free));
  self->ring_2_io_pull_list = s_linked_list_new (FREEFUNC (s_free));
  self->ring_2_workers_list = s_linked_list_new (FREEFUNC (s_free));

  self->teardown_handler_list = s_linked_list_new (FREEFUNC (_s_main_loop_teardown_handerer_obj_free));

  s_mutex_unlock (self->mutex[S_MAIN_LOOP_RING_NONE][S_MAIN_LOOP_STATE_NONE]);

  return self;
}

SMainLoop *
s_main_loop_get_default () {
  if (_default_main_loop == NULL) {
    _default_main_loop = s_main_loop_new ();
  }
  return _default_main_loop;
}

void
s_main_loop_run (SMainLoop * self) {
  assert (self != NULL);
  self->is_run = TRUE;
  while (self->is_run) {
    switch (self->ring_1_state) {
      case (S_MAIN_LOOP_STATE_EVENT):
        _s_main_loop_do_list (self->ring_1_event_handler_list);
        self->ring_1_state = S_MAIN_LOOP_STATE_IO_PUSH;
        break;
      case (S_MAIN_LOOP_STATE_IO_PUSH):
        _s_main_loop_do_list (self->ring_1_io_push_list);
        self->ring_1_state = S_MAIN_LOOP_STATE_IO_PULL;
        break;
      case (S_MAIN_LOOP_STATE_IO_PULL):
        _s_main_loop_do_list (self->ring_1_io_pull_list);
        self->ring_1_state = S_MAIN_LOOP_STATE_RING_TWO_NEXT;
        break;
      case (S_MAIN_LOOP_STATE_RING_TWO_NEXT):
        _s_main_loop_do_ring_two (self);
        self->ring_1_state = S_MAIN_LOOP_STATE_DO_WORKERS;
        break;
      case (S_MAIN_LOOP_STATE_DO_WORKERS):
        _s_main_loop_do_list (self->ring_1_workers_list);
        self->ring_1_state = S_MAIN_LOOP_STATE_SLEEP;
        break;
      case (S_MAIN_LOOP_STATE_SLEEP):
        if(s_mm_get_type () == S_MM_TYPE_MARK_AND_SWEEP) {
          s_mm_mark_and_sweep_sweep ();
        }
        s_usleep (1);
        self->ring_1_state = S_MAIN_LOOP_STATE_EVENT;
        break;
      default:
        s_err_print ("Reaching a place that should not be reached.\n"
                     "Aborting.");
        exit (666);
    }
    s_mutex_lock (self->mutex[S_MAIN_LOOP_RING_NONE][S_MAIN_LOOP_STATE_NONE]);

    sint no_of_handlers = 0;

    no_of_handlers += s_linked_list_len (self->ring_1_event_handler_list);
    no_of_handlers += s_linked_list_len (self->ring_1_io_push_list);
    no_of_handlers += s_linked_list_len (self->ring_1_io_pull_list);
    no_of_handlers += s_linked_list_len (self->ring_1_workers_list);

    no_of_handlers += s_linked_list_len (self->ring_2_event_handler_list);
    no_of_handlers += s_linked_list_len (self->ring_2_io_push_list);
    no_of_handlers += s_linked_list_len (self->ring_2_io_pull_list);
    no_of_handlers += s_linked_list_len (self->ring_2_workers_list);

    if (!self->is_run || no_of_handlers == 0) {
      s_mutex_unlock (self->mutex[S_MAIN_LOOP_RING_NONE][S_MAIN_LOOP_STATE_NONE]);
      break;
    }
    s_mutex_unlock (self->mutex[S_MAIN_LOOP_RING_NONE][S_MAIN_LOOP_STATE_NONE]);
  }
  s_main_loop_quit (self);
}


void
s_main_loop_quit (SMainLoop * self) {
  s_mutex_lock (self->mutex[S_MAIN_LOOP_RING_NONE][S_MAIN_LOOP_STATE_NONE]);
  self->is_run = FALSE;

  s_linked_list_free (self->ring_1_event_handler_list, TRUE);
  s_linked_list_free (self->ring_1_io_push_list, TRUE);
  s_linked_list_free (self->ring_1_io_pull_list, TRUE);
  s_linked_list_free (self->ring_1_workers_list, TRUE);

  s_linked_list_free (self->ring_2_event_handler_list, TRUE);
  s_linked_list_free (self->ring_2_io_push_list, TRUE);
  s_linked_list_free (self->ring_2_io_pull_list, TRUE);
  s_linked_list_free (self->ring_2_workers_list, TRUE);

  s_linked_list_free (self->teardown_handler_list, TRUE);

  for (sint i = 0; i < S_MAIN_LOOP_RING_LAST; i++) {
    for (sint j = 0; j < S_MAIN_LOOP_STATE_LAST; j++) {
      s_print ("[%d][%d]\n",i,j);
      s_mutex_free (self->mutex[i][j]);
    }
  }

  s_mutex_free (self->teardown_handler_mutex);

  s_free (self);
}

void
s_main_loop_ref (SMainLoop * self) {
  self->ref_count++;
}

void
s_main_loop_unref (SMainLoop * self) {
  self->ref_count--;
}

void
s_main_loop_add_event_handler (SMainLoop * self,
                               SMainLoopRing ring,
                               RunFunc event_handler,
                               spointer user_data) {
  //
  s_mutex_lock (self->mutex[ring][S_MAIN_LOOP_STATE_EVENT]);
  if (user_data == NULL) {
    s_err_print ("No user data provided to the event handeler."
                 " This is an error.");
    print_backtrace ();
    return;
  }

  SMainLoopItem * item = s_malloc (sizeof (SMainLoopItem));
  item->state = S_MAIN_LOOP_STATE_EVENT;
  item->callback = event_handler;
  item->user_data = user_data;
  item->is_alive = TRUE;

  switch (ring) {
    case (S_MAIN_LOOP_RING_ONE):
      s_linked_list_append (self->ring_1_event_handler_list, item);
      break;
    case (S_MAIN_LOOP_RING_TWO):
      s_linked_list_append (self->ring_2_event_handler_list, item);
      break;
    default:
      s_err_print ("Invalid value of the ring parameter for "
                   "s_main_loop_add_io_pull function.");
  }
  s_mutex_unlock (self->mutex[ring][S_MAIN_LOOP_STATE_EVENT]);
}

void
s_main_loop_add_io_push (SMainLoop * self,
                         SMainLoopRing ring,
                         RunFunc push_callback,
                         spointer user_data) {
  //
  s_mutex_lock (self->mutex[ring][S_MAIN_LOOP_STATE_IO_PUSH]);
  if (user_data == NULL) {
    s_err_print ("No user data provided to the IO push event."
                 " This is an error.");
    print_backtrace ();
    return;
  }

  SMainLoopItem * item = s_malloc (sizeof (SMainLoopItem));
  item->state = S_MAIN_LOOP_STATE_IO_PUSH;
  item->callback = push_callback;
  item->user_data = user_data;
  item->is_alive = TRUE;

  switch (ring) {
    case (S_MAIN_LOOP_RING_ONE):
      s_linked_list_append (self->ring_1_io_push_list, item);
      break;
    case (S_MAIN_LOOP_RING_TWO):
      s_linked_list_append (self->ring_2_io_push_list, item);
      break;
    default:
      s_err_print ("Invalid value of the ring parameter for "
                   "s_main_loop_add_io_push function.");
  }
  s_mutex_unlock (self->mutex[ring][S_MAIN_LOOP_STATE_IO_PUSH]);
}

void
s_main_loop_add_io_pull (SMainLoop * self,
                         SMainLoopRing ring,
                         RunFunc pull_callback,
                         spointer user_data) {
  //
  s_mutex_lock (self->mutex[ring][S_MAIN_LOOP_STATE_IO_PULL]);
  if (user_data == NULL) {
    s_err_print ("No user data provided to the IO pull event."
                 " This is an error.");
    print_backtrace ();
    return;
  }

  SMainLoopItem * item = s_malloc (sizeof (SMainLoopItem));
  item->state = S_MAIN_LOOP_STATE_IO_PULL;
  item->callback = pull_callback;
  item->user_data = user_data;
  item->is_alive = TRUE;

  switch (ring) {
    case (S_MAIN_LOOP_RING_ONE):
      s_linked_list_append (self->ring_1_io_pull_list, item);
      break;
    case (S_MAIN_LOOP_RING_TWO):
      s_linked_list_append (self->ring_2_io_pull_list, item);
      break;
    default:
      s_err_print ("Invalid value of the ring parameter for "
                   "s_main_loop_add_ function.");
  }
  s_mutex_unlock (self->mutex[ring][S_MAIN_LOOP_STATE_IO_PULL]);
}

void
s_main_loop_add_worker (SMainLoop * self,
                        SMainLoopRing ring,
                        RunFunc worker_callback,
                        spointer user_data) {
  //
  s_mutex_lock (self->mutex[ring][S_MAIN_LOOP_STATE_DO_WORKERS]);
  s_dbg_print ("locked");

  SMainLoopItem * item = s_malloc (sizeof (SMainLoopItem));
  item->state = S_MAIN_LOOP_STATE_DO_WORKERS;
  item->callback = worker_callback;
  item->user_data = user_data;
  item->is_alive = TRUE;

  switch (ring) {
    case (S_MAIN_LOOP_RING_ONE):
      s_linked_list_append (self->ring_1_workers_list, item);
      break;
    case (S_MAIN_LOOP_RING_TWO):
      s_linked_list_append (self->ring_2_workers_list, item);
      break;
    default:
      s_err_print ("Invalid value of the ring parameter for "
                   "s_main_loop_add_worker function.");
  }
  s_mutex_unlock (self->mutex[ring][S_MAIN_LOOP_STATE_DO_WORKERS]);
  s_dbg_print ("Unlocked");
}

void
s_main_loop_add_teardown_handler (SMainLoop * self,
                                  Callback teardown_handler,
                                  spointer self_obj,
                                  spointer user_data) {
  s_mutex_lock (self->teardown_handler_mutex);
  if (self == NULL) {
    s_err_print ("No self pointer provided for the teardown handler "
                 "This is an error.");
    print_backtrace ();
  }

  SMainLoopTeardownHandler * item = s_malloc (sizeof (SMainLoopTeardownHandler));

  item->teardown_handler = teardown_handler;
  item->obj_self = self_obj;
  item->user_data = user_data;

  s_linked_list_append (self->teardown_handler_list, item);
  s_mutex_unlock (self->teardown_handler_mutex);
}

/* ************************************************************************** */

void
_s_main_loop_teardown_handerer_obj_free (SMainLoopTeardownHandler * self) {
  Callback callback = self->teardown_handler;
  callback (self->obj_self, self->user_data);
  s_free (self);
}

void
_s_main_loop_do_list (SLinkedList * event_handler_list) {
  if (s_linked_list_len (event_handler_list) == 0) {
    return;
  }

  s_linked_list_head (event_handler_list);
  do {
    SMainLoopItem * item = s_linked_list_get_current (event_handler_list);
    RunFunc callback = item->callback;
    spointer user_data = item->user_data;

    item->is_alive = (uintptr_t) callback (user_data);
  } while (s_linked_list_next (event_handler_list));

  s_linked_list_head (event_handler_list);
  do {
    SMainLoopItem * item = s_linked_list_get_current (event_handler_list);
    if (!item->is_alive) {
      s_linked_list_remove_current (event_handler_list, TRUE);
    }
  } while (s_linked_list_next (event_handler_list));

}

void
_s_main_loop_do_ring_two (SMainLoop * self) {
  switch (self->ring_2_state) {
    case (S_MAIN_LOOP_STATE_EVENT):
      _s_main_loop_do_list (self->ring_2_event_handler_list);
      self->ring_2_state = S_MAIN_LOOP_STATE_IO_PUSH;
      break;
    case (S_MAIN_LOOP_STATE_IO_PUSH):
      _s_main_loop_do_list (self->ring_2_io_push_list);
      self->ring_2_state = S_MAIN_LOOP_STATE_IO_PULL;
      break;
    case (S_MAIN_LOOP_STATE_IO_PULL):
      _s_main_loop_do_list (self->ring_2_io_pull_list);
      self->ring_2_state = S_MAIN_LOOP_STATE_DO_WORKERS;
      break;
    case (S_MAIN_LOOP_STATE_DO_WORKERS):
      _s_main_loop_do_list (self->ring_2_workers_list);
      self->ring_2_state = S_MAIN_LOOP_STATE_EVENT;
      break;
    default:
      s_err_print ("Reaching a place that should not be reached.\n"
                   "Aborting.");
      exit (666);
  }
}


