#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"
#include "el.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;

  if (strcmp (hostname, "localhost"))
    {
      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;
}

/**
   SELF Object 에 설정이 필요한 요소들에 대한 설정이 이루어지게 됩니다.  예를 
   들면, request_rec * 구조체에 대한 포인터 설정이 예입니다.

   @param self  SELF 객체
   @param r     request_rec 구조체
 */
int
selfobj_beforecall (self, r)
     RvObject *self;
     request_rec *r;
{
  OBJECT_REQUEST (self) = r;

  return 0;
}

/**
   클래스의 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 = GET_STACK_VALUE (OBJREF, &(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)
        {
          if (r)
            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
                || STACK_TYPE (TICKET_LOCAL (tk)) == STACK_TYPE_STRINGREF
                || STACK_TYPE (TICKET_LOCAL (tk)) == STACK_TYPE_ARRAYREF))
            {
              request_add_error (r, ERRLOG_ERR, ERRLOG_MARK,
                                 "The first argument of native method should be a ObjectRef.");
              return -1;
            }

          if (selfobj_beforecall (GET_STACK_VALUE (OBJREF, TICKET_LOCAL (tk)), r))
            return -1;

          if (m->native (GET_STACK_VALUE (OBJREF, 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, tk)
     struct rovm *r;
     struct ticket *tk;
{
  RvObject *o;

  o = (RvObject *) gc_tcalloc (r, tk, sizeof (RvObject), GC_OBJECTREF);
  if (!o)
    return NULL_OBJECT;

  OBJECT_TICKET (o) = tk;
  OBJECT_CTIME (o) = rc_time_now ();

  return o;
}

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

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

  if (!r || !tk || !c)
    return NULL_OBJECT;

  o = init_eobject (r, tk);
  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 (r, tk, 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];

/**
   Array 를 확장합니다.  OBJ 는 반드시 ArrayRef 여야 합니다.
 */
int
rc_expandarray (RvObject *obj, int num)
{
  int curlen, maxlen;
  RvValue *val;

  if (!obj || num < 0)
    return -1;
  if (!OBJECT_IS_ARRAY (obj))
    return -1;
  if (!num)
    return 0;

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

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

  val = rc_getfieldvalue (obj, "value", "[");
  if (!val)
    return -1;

  if (curlen >= maxlen)
    {
      void *newbuf;
      maxlen += num;
      newbuf = (void *) gc_calloc (OBJECTL_ROVM (obj), OBJECT_TICKET (obj), sizeof (RvValue) * maxlen);
      if (!newbuf)
        return -1;
      memcpy (newbuf, GET_STACK_VALUE (ADDR, val), sizeof (RvValue) * curlen);
      GET_STACK_VALUE (ADDR, val) = newbuf;

      if (RvSetField (obj, "maxlen", "I", maxlen))
        return -1;
    }

  return 0;
}

/**
   ArrayRef 의 배열 갯수와 entry 시작점을 계산하여 반환합니다.

   @param obj    ArrayRef
   @param entry  배열 항목이 시작되는 지점
   @param arrlen 배열 항목 갯수.
   @return      성공시 0, 실패시 -1
 */
int
rc_getinfo_array (RvObject *obj, RvValue **entry, int *arrlen)
{
  RvValue *val;

  if (!obj || (obj && !OBJECT_IS_ARRAY (obj)))
    return -1;

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

  val = rc_getfieldvalue (obj, "value", "[");
  if (!val)
    return -1;
  *entry = (RvValue *) GET_STACK_VALUE (ADDR, val);

  return 0;
}

/**
   StringRef 의 문자열 버퍼와 길이를 계산하여 STR 와 LEN 에 각각 저장하여
   반환합니다.

   @param obj   StringRef
   @param str   저장할 문자열 포인터
   @param len   저장항 문자열 길이
   @return      성공시 0, 실패시 -1
 */
int
rc_getinfo_string (RvObject *obj, char **str, int *len)
{
  RvValue *val;

  if (!obj || (obj && !OBJECT_IS_STRING (obj)))
    return -1;

  val = rc_getfieldvalue (obj, "len", "I");
  if (!val)
    return -1;
  *len = GET_STACK_VALUE (INT, val);
  
  val = rc_getfieldvalue (obj, "value", "[");
  if (!val)
    return -1;
  
  *str = RvValue2Str (obj, val);

  return 0;
}

/**
   타입을 FTYPE 으로 가지는 필드 FNAME 을 SELF 에서 찾아 해당 값이 저장되는
   공간 RvValue 구조체를 반환하게 됩니다.

   @param self  SELF 구조체.
   @param fname 필드 이름
   @param ftype 필드 타입
   @return      SELF 에서 존재하는 필드를 위한 공간을 반환합니다.
 */
RvValue *
rc_getfieldvalue (RvObject *self, const char *fname, const char *ftype)
{
  RvField *f;

  if (!self || !fname || !ftype)
    return NULL;

  f = rc_find_efield (OBJECTL_CLASS (self), (u1 *) fname, (u1 *) ftype);
  if (!f)
    return NULL;

  return OBJECTL_FIELD_IDX (self, FIELD_INDEX (f));
}

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

   @param nlem  할당할 요소들의 크기.
 */
RvObject *
NewRvArray (r, tk, nelm)
     struct rovm *r;
     struct ticket *tk;
     int nelm;
{
  RvObject *obj;
  RvValue *val;

  /* ArrayRef 를 할당합니다.  */
  obj = NewRvObject (r, tk, rc_lookup_class (r, "/core/array"));
  if (!obj)
    return NULL_OBJECT;
  OBJECT_IS_ARRAY (obj) = TRUE;

  /* 내부 field 이름인 value 의 경우, 조금 특별히 취급해 줘야 한다.  */
  val = rc_getfieldvalue (obj, "value", "[");
  if (!val)
    return NULL_OBJECT;
  STACK_TYPE (val) = STACK_TYPE_ARRAYREF;
  STACK_IS_ARRAYVALUE (val) = TRUE;
  GET_STACK_VALUE (ADDR, val) = (void *) gc_calloc (r, tk, sizeof (RvValue) * nelm);
  if (!GET_STACK_VALUE (ADDR, val))
    return NULL_OBJECT;

  if (RvSetField (obj, "len", "I", nelm))
    return NULL_OBJECT;

  if (RvSetField (obj, "maxlen", "I", nelm))
    return NULL_OBJECT;

  return obj;
}

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

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

  /* String ObjectRef 를 할당합니다.  */
  obj = NewRvObject (r, tk, rc_lookup_class (r, "/core/str"));
  if (!obj)
    return NULL_OBJECT;
  OBJECT_IS_STRING (obj) = TRUE;

  /* 문자열의 value 값은 특별하게 관리해줘야 한다.  */
  val = rc_getfieldvalue (obj, "value", "[");
  if (!val)
    return NULL_OBJECT;
  STACK_TYPE (val) = STACK_TYPE_ARRAYREF;
  STACK_IS_STRINGVALUE (val) = TRUE;
  GET_STACK_VALUE (ADDR, val) = (void *) gc_calloc (r, tk, len);
  if (!GET_STACK_VALUE (ADDR, val))
    return NULL_OBJECT;
  if (str)
    memcpy (GET_STACK_VALUE (ADDR, val), str, len);

  if (RvSetField (obj, "len", "I", len))
    return NULL_OBJECT;

  return obj;
}
