#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 "file.h"
#include "gc.h"

/**
   Class Tree 에 key 와 value 가 (PATH, CLS) 인 node 를 등록합니다.

   @param r     ROVM 구조체
   @param path  Class 의 경로
   @param cls   로드된 Class 포인터
   @return      성공적으로 등록되었을 경우, RC_SUCCESS 를 반환하게 되며,
                그렇지 않을 경우, -1 을 반환하게 됩니다.
*/
rc_status_t
rc_register_class (r, path, cls)
     struct rovm *r;
     char *path;
     RvClass *cls;
{
  if (!clstree_lookup (ROVM_CLSTREE (r), path))
    {
      char *dupstr = rc_strdup (path);

      /* Insert 과정이므로 이에 대한 serialize (한줄 세우기) 과정이 필요하다.

         ??? 현재 tktree 와 clstree 가 같은 mutex 를 사용하여 locking 을
             하고 있는데, 이들을 각각의 서로 다른 mutex 를 사용하도록 고쳐주는
             것도 좋은 생각일 것 같다.  서로 영향을 미치는 요소는 존재하지
             않는다.  */
      rc_thread_mutex_lock (ROVM_MUTEX (r));

      if (clstree_insert (ROVM_CLSTREE (r), dupstr, (void *) cls))
        {
          rc_thread_mutex_unlock (ROVM_MUTEX (r));
          return -1;
        }

      rc_thread_mutex_unlock (ROVM_MUTEX (r));
    }

  return RC_SUCCESS;
}

/**
   PATH 에 존재하는 class 파일을 실제로 로드하여 Class Tree 에 등록합니다.

   @param r     ROVM 구조체
   @param path  찾을 Class 경로
   @return      성공적으로 로드하였을 경우, 해당 Class 포인터를 그렇지 않을 경우,
                NULL_CLASS 를 반환합니다.
*/
RvClass *
rc_load_class (r, path)
     struct rovm *r;
     char *path;
{
  rc_status_t rv;
  RvClass *cls;

  if (!r || !path)
    return NULL_CLASS;

  cls = rc_load_classfile (r, path);
  if (!cls)
    return NULL_CLASS;

  rv = rc_register_class (r, path, cls);
  if (rv != RC_SUCCESS)
    return NULL_CLASS;

  return cls;
}

/**
   구조체 R 에 존재하는 Class Tree 에서 PATH 에 해당하는 Class 를 찾아, 그에
   맞는 RvClass 구조체를 반환합니다.

   만약 Class Tree 에서 해당 Class 를 찾지 못하였다면, 해당 path 에 대한 
   loading 을 수행하게 됩니다.

   @param r     ROVM 구조체
   @param path  찾을 Class 경로
   @return      해당 Class 를 찾고 로드되었다면 해당 클래스 포인터를 반환합니다.
                그렇지 않을 경우, NULL_CLASS 를 반환합니다.
 */
RvClass *
rc_lookup_class (r, path)
     struct rovm *r;
     char *path;
{
  RvClass *cls;

  if (!r || !path)
    return NULL_CLASS;

  cls = (RvClass *) clstree_lookup (ROVM_CLSTREE (r), path);
  if (cls)
    return cls;

  return rc_load_class (r, path);
}

/**
   IPTYPE 와 HOSTNAME, PORT, PATH 를 이용하여 그에 대한 class 정보를 
   얻는다.  만약 HOSTNAME 이 로컬 머신의 IP (혹은 hostname) 일 경우는,
   실제 해당 class 를 로드하여 그에 대한 값을 반환한다.

   @param r             request_rec 구조체
   @param iptype        IPv4 인지 IPv6 인지를 가르킨다.  IPv6 는 아직 공식적으로
                        지원하지는 않는다.
   @param hostname      Host 이름
   @param port          Host 포트 번호
   @param path          찾고자하는 class 경로.
   @return              만약 class 가 존재하지 않을 경우, NULL_CLASS 값을
                        반환하고, 만약 다른 host 의 요청이 왔을 경우, 
                        REMOTE_CLASS 값을 반환한다.  그렇지 않을 경우 load 된
                        class 의 포인터값을 반환한다.
*/
RvClass *
rc_getclassbyhost (r, iptype, hostname, port, path)
     request_rec *r;
     int iptype;
     char *hostname, *path;
     unsigned short port;
{
  rc_status_t rv;
  rc_sockaddr_t *sa;

  rv = rv_sockaddr_info_get (&sa, hostname, RC_INET, 
                             port ? port : DEFAULT_ROVM_LISTENING_PORT,
                             0, REQUEST_POOL (r));
  if (rv != RC_SUCCESS)
    return NULL_CLASS;

  if (!rc_sockaddr_equal (CONN_LOCAL_ADDR (REQUEST_CONN (r)), sa))
    return REMOTE_CLASS;

  return rc_lookup_class (REQUEST_ROVM (r), path);
}

/**
   Class CLS 에서 메쏘드의 이름과 타입이 NAME 이고, TYPE 인 메쏘드를 찾아
   그에 대한 포인터를 반환한다.
 */
RvMethod *
rc_findmethod (cls, name, type)
     RvClass *cls;
     char *name, *type;
{
  RvMethod *m;

  if (!cls || !name || !type)
    return NULL_METHOD;

  m = CLASS_METHOD (cls);

  while (m)
    {
      if (!strcmp (name, METHOD_NAME (m))
          && !strcmp (type, MTYPE_STR (METHOD_TYPE (m))))
        return m;

      m = METHOD_NEXT (m);
    }

  return NULL_METHOD;
}

/**
   클래스의 Method 를 호출한다.

   @return      만약 오류가 발생하였을 경우, -1 을 그렇지 않다면 0 을 반환한다.
 */
int
rc_callmethod (r, tk, name, type, argc, argv)
     request_rec *r;
     struct ticket *tk;
     char *name, *type;
     u2 argc, *argv;
{
  RvObject *obj;

  obj = (RvObject *) STACK_V_ADDR (&(TICKET_SP (tk)[-(argc - 1)]));

  if (OBJECT_TYPE (obj) == LOCAL_OBJECT)
    {
      RvClass *cls = OBJECTL_CLASS (obj);
      RvMethod *m;

      m = rc_findmethod (cls, name, type);
      if (!m)
        {
          request_add_error (r, ERRLOG_ERR, ERRLOG_MARK,
                             "Can't find such method.");
          return -1;
        }

      /* 메쏘드를 호출하기 전에, 필요한 ticket 설정을 할 수 있도록 한다.  */
      if (ticket_beforecall (tk, m, argc, argv))
        return -1;

      /* 만약 호출하고자 하는 method 가 native 형식일 경우는, 해당 함수를 호출할 수
         있도록 배려를 해줘야 한다.  */
      if (METHOD_FLAG (m) & ACCESS_NATIVE)
        {
          if (STACK_TYPE (TICKET_LOCAL (tk)) != STACK_TYPE_OBJREF)
            {
              request_add_error (r, ERRLOG_ERR, ERRLOG_MARK,
                                 "The first argument of native method should be a ObjectRef.");
              return -1;
            }

          if (m->native ((RvObject *) STACK_V_ADDR (TICKET_LOCAL (tk)), TICKET_LOCAL (tk) + 1, TICKET_RET (tk)))
            return -1;
        }
      else
        {
          /* 일반적인 method 호출의 경우.  */
          if (rovmcore_main (r, tk, METHOD_OP (m), METHOD_OPLEN (m)))
            return -1;
        }
    }
  else if (OBJECT_TYPE (obj) == REMOTE_OBJECT)
    {
      request_add_error (r, ERRLOG_ERR, ERRLOG_MARK,
                         "Calling remote method is under construction.");
      return -1;
    }
  else
    {
      request_add_error (r, ERRLOG_ERR, ERRLOG_MARK,
                         "Invalid Object Type.");
      return -1;
    }

  return 0;
}

/**
   RvObject 구조체를 새롭게 할당하며, Local 용과 Remote 용의 공통부분만을
   설정합니다.

   @return 공통적인 것만 설정된 상태로의 RvObject 구조체 포인터를 반환합니다.
 */
RvObject *
init_eobject (r)
     struct rovm *r;
{
  RvObject *o;

  o = (RvObject *) gc_alloc (r, sizeof (RvObject));
  if (!o)
    return NULL_OBJECT;

  memset (o, '\0', sizeof (RvObject));

  OBJECT_CTIME (o) = rc_time_now ();

  return o;
}

/**
   클래스 C 에 대한 ObjectRef 를 생성하여 반환합니다.

   @param c     ObjectRef 를 생성할 클래스 포인터
   @return      생성된 ObjectRef
 */
RvObject *
NewRvObject (c)
     RvClass *c;
{
  RvObject *o;

  o = init_eobject (CLASS_ROVM (c));
  if (!o)
    return NULL_OBJECT;

  OBJECT_TYPE (o) = LOCAL_OBJECT;
  OBJECTL_CLASS (o) = c;

  /* Field 를 위한 ObjectRef 의 공간을 확보한다.  */
  if (CLASS_FIELD_COUNT (c) > 0)
    OBJECTL_FIELD (o) = (RvValue *) gc_calloc (CLASS_ROVM (c), 
                                               CLASS_FIELD_COUNT (c) * sizeof (RvValue));

  return o;
}

/**
   원격에 존재하는 클래스에 대해서 ObjectRef 를 생성하여 반환합니다.

   @param r             Request 구조체
   @param iptype        IPv4 인지 IPv6 인지를 결정합니다.
   @param hostname      접근할 Host Name
   @param port          접근한 Host 의 포트 번호
   @param path          클래스의 경로
   @return              원격에서 생성된 ObjectRef 에 대한 정보를 가지는
                        로컬 ObjectRef
 */
RvObject *
NewRemoteRvObject (r, iptype, hostname, port, path)
     request_rec *r;
     int iptype;
     char *hostname, *path;
     unsigned short port;
{
  return NULL_OBJECT;
}

extern int rovm_stack_size[MAX_STACK_TYPE];

/**
   ArrayRef 를 새롭게 생성하여 그에 대한 포인터를 반환한다.  GC 메모리를
   사용하는 것이 특징이다.

   @param type  ArrayRef 의 타입 (정수형? 실수형? 문자형?)
   @param nlem  할당할 요소들의 크기.
 */
RvValue *
NewRvArray (r, type, nelm)
     struct rovm *r;
     char type;
     int nelm;
{
  RvValue *v;

  if (type >= MAX_STACK_TYPE)
    return NULL_VALUE;

  v = (RvValue *) gc_alloc (r, sizeof (RvValue));
  if (!v)
    return NULL_VALUE;

  STACK_TYPE (v) = STACK_TYPE_ARRAYREF;
  STACK_IS_ARRAY (v) = 1;
  STACK_SUBTYPE (v) = (int) type;
  STACK_ARRAYLEN (v) = nelm;
  STACK_V_ADDR (v) = (void *) gc_calloc (r, rovm_stack_size[(int) type] * nelm);
  if (!STACK_V_ADDR (v))
    return NULL_VALUE;

  return v;
}

/**
   길이가 LEN 인 문자열 STR 을 ObjectRef 로 변환하여 반환한다.  STR 은 NULL
   로 끝나지 않습니다.

   @param r     Request 구조체
   @param tk    Ticket 구조체
   @param str   문자열 포인터
   @param len   문자열 길이
 */
RvObject *
NewRvStringObject (r, str, len)
     struct rovm *r;
     const char *str;
     size_t len;
{
  RvObject *obj;
  RvClass *cls;
  RvField *fld;
  RvValue *v;

  /* String 클래스를 찾습니다.  */
  cls = rc_lookup_class (r, "/core/str");
  if (!cls)
    return NULL_OBJECT;

  /* String ObjectRef 를 할당합니다.  */
  obj = NewRvObject (cls);
  if (!obj)
    return NULL_OBJECT;

  OBJECT_IS_STRING (obj) = TRUE;

  fld = rc_find_efield (cls, "value", "[");
  if (!fld)
    return NULL_OBJECT;

  v = NewRvArray (r, STACK_TYPE_CHAR, len);
  if (!v)
    return NULL_OBJECT;
  memcpy (STACK_V_ADDR (v), str, len);

  /*
    아래의 과정에서 가장 주의해야 할 점은 ObjectRef 에는 이미 해당 Field 를 위한 공간이
    확보되어 있다는 점이다.  str 클래스의 value [ 를 설정하기 위해 NewRvArray () 함수를
    호출하게 되면 공간이 서로 각각 할당되게 되는데, 이 문제를 피해 주어야 한다.

     NewRvArray () 호출의 경우, 현재 두 개의 GC 메모리를 할당하게 되는데, 
     아래의 복사 과정에서 STACK_V_ADDR (v) 는 계속 머물게 되고, v 에 할당된
     메모리는 GC 되어 진다.  (생각 안하면 복잡한 구조.)  */
  *OBJECTL_FIELD_IDX (obj, FIELD_INDEX (fld)) = *v;

  fld = rc_find_efield (cls, "len", "I");
  if (!fld)
    return NULL_OBJECT;

  v = OBJECTL_FIELD_IDX (obj, FIELD_INDEX (fld));
  STACK_TYPE (v) = STACK_TYPE_INT;
  STACK_V_INT (v) = len;

  return obj;
}
