#include <stdlib.h>

#include "types.h"
#include "mpool.h"
#include "tktree.h"
#include "clstree.h"
#include "sha1.h"
#include "listen.h"
#include "rovm.h"

#include "thread.h"
#include "thread_mutex.h"
#include "thread_cond.h"

#include "mpm_worker_fdqueue.h"
#include "mpm_worker_pod.h"
#include "mpm_worker.h"

#include "connection.h"
#include "request.h"
#include "proc_rc.h"

#include "log.h"
#include "common.h"
#include "ticket.h"
#include "utils.h"
#include "gc.h"

extern rc_gc_t *init_ggc (void (*ggc_marker)    (rc_gc_t *G),
                          void (*ggc_finalizer) (rc_gc_t *G, const char *, void *), 
                          void *);
extern void *ggc_getarg (rc_gc_t *);
extern void ggc_setarg_always_collect (rc_gc_t *);
extern void ggc_collect (rc_gc_t *);
extern void *ggc_alloc_stat (rc_gc_t *, size_t);
extern void *ggc_alloc_typed_stat (rc_gc_t *, int, size_t);
extern void ggc_free (void *);
extern void ggc_setarg_quiet_flag (rc_gc_t *, int);
extern int ggc_set_mark (rc_gc_t *, const void *);

extern int rovm_gc_mark_arrayref (struct rovm *, RvValue *, int);
extern int rovm_gc_mark_objectref (struct rovm *, RvObject *);

/**
   Thread 의 argument 구조체입니다.
 */
struct thread_gc
{
  /** Thread 에서 사용할 메모리 pool.  */
  rc_pool_t *p;
  /** Thread Attribute.  */
  rc_threadattr_t *threadattr;

  /** ROVM 구조체.  */
  struct rovm *r;
};

void
rovm_gc_finalizer (rc_gc_t *G, const char *zonename, void *ptr)
{
  int argc;
  u2 argv[1];
  struct ticket *tk;
  struct rovm *r = (struct rovm *) ggc_getarg (G);
#define FINALIZER_TYPE  "(T)V"

  if (zonename[0] != 'O')
    return;
  if (strcmp (zonename, "ObjRef zone"))
    return;

  /* 우리는 아래에서 PTR 이 ObjRef 라고 가정을 하고 코드를 작성해 나갔습니다.  */
  argc = 1;
  argv[0] = 1;

  tk = create_ticket (r);
  if (!tk)
    return;

#define PUSH(N)                         TICKET_SP (tk) += (N);
#define	STACK(N)                        (&TICKET_SP (tk)[-(N)])
#define MOVE_OBJREF(DST, SRC)           SET_STACK_VALUE (DST, OBJREF, (void *) (SRC))
  
  /* 기존 함수를 사용할 수 있도록 하기 위해서, 모양을 똑같이 맞추어 줍니다.  */
  PUSH (1);
  MOVE_OBJREF (STACK (0), ptr);

  /* DOIT - 아래의 과정에서 발생하는 오류가 있을 경우, 그것은 무시해 버립니다.
     현재 아래의 과정에서 발생할 수 있는 오류는 __del__ 메쏘드를 찾을 수 없을 경우에 
     해당하는 것입니다.  그 외의 과정에서 오류가 발생한다면 큰 문제입니다.  */
  rc_callmethod (NULL, tk, "__del__", FINALIZER_TYPE, argc, argv);

  destroy_ticket (tk);

#undef PUSH
#undef STACK
#undef MOVE_OBJREF
}

/**
   만약 P 가 marking 이 안된 GC 메모리일 경우, 해당 메모리를 marking 하고
   값 0 을 반환합니다.  그렇지 않을 경우, 1 을 반환하게 됩니다.

   @param r     ROVM 구조체
   @param p     Marking 할 메모리 포인터.  반드시 GC 메모리 포인터야 한다.
 */
int
gc_set_mark (r, p)
     struct rovm *r;
     const void *p;
{
  return ggc_set_mark (ROVM_GC (r), p);
}

/**
   하나의 Stack Slot 을 점검한다.  RvValue 구조체와 rovm_stack_t 구조체는 
   같은 구조체임을 알기 바란다.

   @param r      ROVM 구조체
   @param v      Stack slot
 */
int
rovm_gc_check_stack (r, v)
     struct rovm *r;
     RvValue *v;
{
  if (!r || !v)
    return 0;

  switch (STACK_TYPE (v))
    {
    case STACK_TYPE_STRINGREF:
    case STACK_TYPE_OBJREF:
    case STACK_TYPE_ARRAYREF:
      if (rovm_gc_mark_objectref (r, GET_STACK_VALUE (OBJREF, v)))
        return -1;
      break;
    default:
      break;
    }

  return 0;
}

/**
   하나의 ObjectRef 를 마킹한다.

   @param r     ROVM 구조체
   @param o     마킹할 ObjectRef 구조체
   @return      성공시 0, 실패시 -1
 */
int
rovm_gc_mark_objectref (r, o)
     struct rovm *r;
     RvObject *o;
{
  int i;
  RvValue *val;

  if (!r || !o)
    return 0;

  /* 해당 포인터가 만약 아직 marking 이 이루어지지 않았다면, mark 하고 이미 이루어졌다면
     돌아간다.  */
  if (gc_set_mark (r, o))
    return 0;

  switch (OBJECT_TYPE (o))
    {
    case LOCAL_OBJECT:
      {
        if (OBJECT_IS_STRING (o))
          {
            /* StringRef 일 경우, 손수 하나씩 설정해 준다.  */
            val = rc_getfieldvalue (o, "value", "[");
            if (!val)
              return -1;
            if (gc_set_mark (r, GET_STACK_VALUE (ADDR, val)))
              return 0;
          }
        else if (OBJECT_IS_ARRAY (o))
          {
            int len;
            RvValue *entry;

            /* ArrayRef 일 경우도 마찬가지다.  */
            val = rc_getfieldvalue (o, "value", "[");
            if (!val)
              return -1;
            entry = (RvValue *) GET_STACK_VALUE (ADDR, val);

            val = rc_getfieldvalue (o, "len", "I");
            if (!val)
              return -1;
            len = GET_STACK_VALUE (INT, val);

            for (i=0; i<len; i++)
              {
                if (rovm_gc_check_stack (r, entry + i))
                  return -1;
              }
            /* 여전히 공사해야 할 부분.  */
            if (gc_set_mark (r, entry))
              return 0;
          }
        else
          {
            /* 만약 위에서 처리하게 되는 `특별히 다뤄줘야하는 ObjectRef' 가 아닐 경우를
               처리한다.  */
            if (CLASS_FIELD_COUNT (OBJECTL_CLASS (o)) > 0)
              {
                /* Field 에 들어 있는 값이 ObjectRef 혹은 ArrayRef 일 경우, 해당 사항 또한
                   marking 해야 한다.  재귀적으로 이루어진다.  */
                for (i=0; i<CLASS_FIELD_COUNT (OBJECTL_CLASS (o)); i++)
                  {
                    if (rovm_gc_check_stack (r, OBJECTL_FIELD_IDX (o, i)))
                      return -1;
                  }
              }
          }

        /* Field 값들을 저장하는 공간을 marking 한다.  위 구문과 포인터의 시작점이 같은
           관계로 이를 뒤에서 marking 해줘야 오류가 없다.  */
        if (OBJECTL_FIELD (o) && gc_set_mark (r, OBJECTL_FIELD (o)))
          return 0;
        break;
      }
    case REMOTE_OBJECT:
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
		"Remote ObjectRef's GC is under construction.");
      break;
    default:
      return -1;
    }

  return 0;
}

#if 0
/**
   하나의 ArrayRef 를 마킹한다.

   @param r      ROVM 구조체
   @param o      마킹할 ArrayRef 구조체
   @param is_ret 만약 현재 return 값을 처리할 경우는 조금 특별히 다루어 주어야 한다.
   @return       성공시 0, 실패시 -1
 */
int
rovm_gc_mark_arrayref (r, v, is_ret)
     struct rovm *r;
     RvValue *v;
     int is_ret;
{
  int i;

  if (!r || !v)
    return 0;

  /** ArrayRef 인지를 검사합니다.  */
  if (STACK_TYPE (v) != STACK_TYPE_ARRAYREF)
    return -1;

  if (!is_ret && gc_set_mark (r, v))
    return 0;

  /*
    ArrayRef 가 가질 수 있는 값 종류 중 내부적으로 GC 메모리를 가질 수 있는
    경우의 수를 계산하여 내부 값들을 모두 marking 해줘야 합니다.
   */
  switch (STACK_SUBTYPE (v))
    {
    case STACK_TYPE_STRINGREF:
    case STACK_TYPE_OBJREF:
      {
        RvObject **o = (RvObject **) STACK_V_ADDR (v);

        for (i=0; i<STACK_ARRAYLEN (v); i++)
          {
            if (rovm_gc_mark_objectref (r, o[i]))
              return -1;
          }
        break;
      }
    case STACK_TYPE_ARRAYREF:
      {
        RvValue **v2 = (RvValue **) STACK_V_ADDR (v);

        for (i=0; i<STACK_ARRAYLEN (v2[i]); i++)
          {
            if (rovm_gc_mark_arrayref (r, v2[i], 0))
              return -1;
          }
        break;
      }
    default:
      break;
    }

  /* 이 부분이 아랫단에 있는 이유는 위에서 사용하는 포인터가 STACK_V_ADDR (v) 부터
     시작하기 때문에, 위에서 이것을 marking 하면 내부 검색이 안되기 때문에 여기 있다.  */
  if (STACK_V_ADDR (v) && gc_set_mark (r, STACK_V_ADDR (v)))
    return 0;

  return 0;
}
#endif

/**
   Ticket Tree 의 walking 을 수행할 때, 호출되는 callback 함수입니다.
   이 부분에서 Ticket Tree 에 대한 marking 과정이 이루어지게 됩니다.

   @param node  해당 node 에 대한 포인터 정보.
 */
int
rovm_gc_tktree_walking_cb (tktree_node_t node, void *arg)
{
  int i;
  struct rovm *r = (struct rovm *) arg;
  struct ticket *tk;
  rovm_stack_t *p;

  tk = (struct ticket *) node->value;

  /* 첫번째 단계는 Stack Slot 에 있는 것을 점검합니다.  */
  for (p = TICKET_STACK (tk); p <= TICKET_SP (tk); p++)
    {
      if (rovm_gc_check_stack (r, p))
        return -1;
    }
  
  /* 두번째 단계는 Stack Return Slot 에 있는 것을 점검합니다.  */
  if (rovm_gc_check_stack (r, TICKET_RET (tk)))
    return -1;

  /* 세번째 단계는 Delayed Stack Slot 에 있는 것을 점검합니다.  */
  for (i=0; i<TICKET_DELAYED_SLOT_NELM (tk); i++)
    gc_set_mark (r, TICKET_DELAYED_SLOT_IDX (tk, i));

  return 0;
}

/**
   Marker callback 함수입니다.  이 함수는 ggc_collect () 에 의해 실제
   Garbage Collection 이 일어날 때 자동으로 호출되는 함수이며, init_ggc ()
   함수의 첫번째 인자로써 건네지게 됩니다.

   @param G     GC 메인 구조체에 대한 포인터
 */
void
rovm_gc_mark_roots (rc_gc_t *G)
{
  struct rovm *r = (struct rovm *) ggc_getarg (G);

  /*
    이 함수가 호출되었을 때는 Garbage Collector 에 의해 할당된 모든 메모리
    entry 의 marking 이 clean 된 상태일 것입니다.

    아래에서는 이제 살아있는 것으로 간주되는 GC 메모리들을 일일이 모두 체크
    해주어야 합니다.  현재 아래와 같은 요소들이 GC 메모리를 사용하고 있습니다.

      1) Object Reference
         모든 Object 들이 GC 메모리를 사용하도록 합니다.  Object Ref 가
         VM 을 실행하는 동안 가장 빈번하게 할당되며, 사라지기 때문입니다.

      2) Array Reference
         메쏘드나 기타 여러 경로로 통해서 ArrayRef 가 만들어질 수 있습니다.
         이에 대한 marking 이 필요합니다.
  */

  /*
    ROVM Server 의 Garbage Collection 전략.

    1) Object Reference 에 대한 Garbage Collection 전략.

       모든 ObjectRef 는 Ticket 구조체의 Stack 슬롯에 들어가도록 설계
       되어 있습니다.

       즉, 만약 ObjectRef 가 살아있는 것이라며

         검색범위 = (Ticket 구조체의 sp 변수 - Ticket 구조체의 stack 변수)
         검색범위 += (Ticket 구조체의 ret 변수)

       위의 검색범위에 항상 들어가 있을 것입니다.  만약 위 범위에 존재하지
       않는다면 죽은 ObjectRef 일 것입니다.

       검색범위의 Stack 슬롯에서 STACK_TYPE (NODE) 이 STACK_TYPE_OBJREF
       와 STACK_TYPE_ARRAYREF 인 것만 marking 을 하게 됩니다.  
       나머지 marking 이 되지 않는 것들은 모두 메모리 해제될 것입니다.

       여기서 하나 더 생각해야할 문제가 생겼는데, ``ENVLANG File Format'' v0.2
       에 들어서면서 클래스에 Field 값이 생겼다는 사실입니다.  이로 인해서
       Field 값에 또한 GC 메모리가 설정될 수 있기 때문에, 각각의 ObjectRef 에서
       해당 field 들을 점검한 후, 내부에 STACK_TYPE_OBJREF 와 STACK_TYPE_ARRAYREF
       가 설정되어 있으면 그것 또한 marking 을 해줘야 하는 부분을 생각해야
       합니다.

       만약 Garbage Collection 이 이루어지는 동안, 다른 쓰레드에 의해 sp
       값이 변경되게 되면 그것은 문제가 되지 않습니다.  왜냐하면 아래와 같은
       경우가 존재할 수 있기 때문입니다.

         a. 만약 sp 변경된다고 하더라고, 살아있는 ObjectRef 는 그대로 marking
            이 될 것이며, 죽은 ObjectRef 가 marking 이 그 때 안되더라도,
            다음 Garbage Collection 에서는 사라질 것이다.

    2) Array Reference 에 대한 Garbage Collection 전략.

       현재 ROVM Server 에서 ArrayRef 가 위치할 수 있는 곳은 아래와 같은 곳들입니다.

         * Ticket 구조체의 Stack 슬롯
         * Ticket 구조체의 Return Stack 슬롯
         * ObjectRef 의 필드 내부

       위의 해당 항목들을 모두 marking 하는 것입니다. 세번째 항목의 경우,
       Ticket 구조체의 Stack 슬롯들에 대한 타입 검사를 하고 이를 marking
       하게 됩니다.
  */

  /* Ticket Tree 을 모두 walking 해 봅시다.  */
  safe_tktree_walking (r, ROVM_TKTREE (r), rovm_gc_tktree_walking_cb, (void *) r);
}

/**
   Garbage Collector Thread 의 main 함수.

   @param thd   Thread ID
   @param arg   인자. (struct thread_gc 구조체)
 */
static void * 
gc_threads (rc_thread_t *thd, void *arg)
{
  struct thread_gc *tg = (struct thread_gc *) arg;

  if (!tg || (tg && !tg->r))
    {
      rc_thread_exit (thd, RC_SUCCESS);
      return NULL;
    }

  while (1)
    {
      /*
        Garbage Collection 이 일어나는 중간에는 gc_alloc () 함수를 통한
        메모리 할당이 전면 중지됩니다.
      */
      rc_thread_mutex_lock (ROVM_GC_MUTEX (tg->r));

      /* Ticket 구조체에 대한 Garbage Collection 을 수행합니다.  */
      ticket_collect (tg->r);
      /* ObjectRef, StringRef, ArrayRef 에 대한 Garbage Collection 을
         수행합니다.  */
      ggc_collect (ROVM_GC (tg->r));

      fprintf (stderr, "gcdone\n");

      rc_thread_mutex_unlock (ROVM_GC_MUTEX (tg->r));

      rc_sleep (rc_time_make (0, rc_time_from_sec (DEFAULT_GC_SLEEP_TIME)));
    }

  rc_thread_exit (thd, RC_SUCCESS);

  return NULL;
}

/**
   Garbage Collector Thread 를 실행한다.

   @param r     ROVM 구조체
   @return      만약 성공적으로 실행시 0 을, 그렇지 않을 경우, -1 을 반환한다.
 */
int
gc_run (r)
     struct rovm *r;
{
  rc_status_t rv;
  rc_thread_t *gc_thread_id;
  rc_threadattr_t *thread_attr;
  rc_pool_t *p;
  struct thread_gc *tg;

  mp_create (&p, PROCREC_PCONF (ROVM_PROCESS (r)));

  rc_threadattr_create (&thread_attr, p);
  /* 0 는 PTHREAD_CREATE_JOINABLE 를 의미한다.  */
  rc_threadattr_detach_set (thread_attr, 0);

  tg = (struct thread_gc *) mp_alloc (p, sizeof (*tg));
  tg->threadattr = thread_attr;
  tg->p = p;
  tg->r = r;

  rv = rc_thread_create (&gc_thread_id, thread_attr, gc_threads, tg, p);
  if (rv != RC_SUCCESS)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
		"rc_thread_create: unable to create worker thread");
      return -1;
    }

  return 0;
}

void *
gc_talloc (r, tk, size, type)
     struct rovm *r;
     struct ticket *tk;
     size_t size;
     enum gc_type type;
{
  void *p;

  /*
    ggc_collect () 함수가 실행되는 동안에는 메모리 할당을 하지 못하도록
    막게됩니다.
   */
  rc_thread_mutex_lock (ROVM_GC_MUTEX (r));

  p = ggc_alloc_typed_stat (ROVM_GC (r), type, size);
  if (!p)
    {
      rc_thread_mutex_unlock (ROVM_GC_MUTEX (r));
      return NULL;
    }

  /* GC 메모리가 할당되었는데, 이제 unlock 되기 전에, GC thread 가 marking
     할 수 있는 위치에 이 메모리를 올려두자.  그래야 marking 이 제대로 될것이다.  
     그렇지 않을 경우, 예측하지 못하는 exception 상황이 발생할 수 있다.  */
  if (TICKET_DELAYED_SLOT_NELM (tk) >= TICKET_DELAYED_SLOT_MAX (tk))
    {
      TICKET_DELAYED_SLOT_MAX (tk) += DEFAULT_DELAYED_SLOT;
      TICKET_DELAYED_SLOT (tk) = (void **) realloc (TICKET_DELAYED_SLOT (tk), 
                                                    TICKET_DELAYED_SLOT_MAX (tk) * sizeof (void *));
    }
  TICKET_DELAYED_SLOT_IDX (tk, TICKET_DELAYED_SLOT_NELM (tk)++) = p;

  rc_thread_mutex_unlock (ROVM_GC_MUTEX (r));

  return p;
}

/**
   GC 메모리를 할당합니다.

   @param r     ROVM 구조체
   @param size  할당할 메모리 크기
 */
void *
gc_alloc (r, tk, size)
     struct rovm *r;
     struct ticket *tk;
     size_t size;
{
  return gc_talloc (r, tk, size, -1);
}

/**
   GC 메모리를 할당하고 난 후, '\0' 으로 초기화한다.

   @param r     ROVM 구조체
   @param tk    Ticket 구조체
   @param size  할당할 메모리 크기
*/
void *
gc_calloc (r, tk, size)
     struct rovm *r;
     struct ticket *tk;
     size_t size;
{
  void *p;

  p = gc_alloc (r, tk, size);
  if (!p)
    return NULL;

  memset (p, '\0', size);

  return p;
}

void *
gc_tcalloc (r, tk, size, type)
     struct rovm *r;
     struct ticket *tk;
     size_t size;
     enum gc_type type;
{
  void *p;

  p = gc_talloc (r, tk, size, type);
  if (!p)
    return NULL;

  memset (p, '\0', size);

  return p;
}

/**
   GC 메모리를 이용하는 strdup 함수입니다.

   @param r     ROVM 구조체
   @param tk    Ticket 구조체
   @param s     복사할 문자열
   @param l     복사할 문자열 길이.
 */
char *
gc_strndup (r, tk, s, l)
     struct rovm *r;
     struct ticket *tk;
     char *s;
     size_t l;
{
  char *p;

  p = (char *) gc_alloc (r, tk, l + 1);
  if (!p)
    return NULL;

  memcpy (p, s, l);
  p[l] = '\0';

  return p;
}

/**
   GC 메모리를 해제합니다.  바로 해제되지는 않으며, 단순히 해당 gc 항목을
   더럽히기만 합니다.
 */
void
gc_free (p)
     void *p;
{
  ggc_free (p);
}

/**
   ROVM 의 Garbage Collector 를 실행시킨다.

   @param r     ROVM 구조체
 */
int
init_rovm_gc (r, p)
     struct rovm *r;
     rc_pool_t *p;
{
  rc_status_t rv;

  ROVM_GC (r) = init_ggc (rovm_gc_mark_roots, rovm_gc_finalizer, r);
  if (!ROVM_GC (r))
    return -1;

  ggc_setarg_always_collect (ROVM_GC (r));
  ggc_setarg_quiet_flag (ROVM_GC (r), 1);

  rv = rc_thread_mutex_create (&ROVM_GC_MUTEX (r), RC_THREAD_MUTEX_DEFAULT, p);
  if (rv != RC_SUCCESS)
    return -1;
 
  return 0;
}
