/*
** lupb_msg -- Message/Array/Map objects in Lua/C that wrap upb/msg.h
*/

#include "upb/msg.h"

#include <float.h>
#include <math.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include "lauxlib.h"
#include "upb/bindings/lua/upb.h"
#include "upb/reflection.h"
#include "upb/text_encode.h"

#include "upb/port_def.inc"

/*
 * Message/Map/Array objects.  These objects form a directed graph: a message
 * can contain submessages, arrays, and maps, which can then point to other
 * messages.  This graph can technically be cyclic, though this is an error and
 * a cyclic graph cannot be serialized.  So it's better to think of this as a
 * tree of objects.
 *
 * The actual data exists at the upb level (upb_msg, upb_map, upb_array),
 * independently of Lua.  The upb objects contain all the canonical data and
 * edges between objects.  Lua wrapper objects expose the upb objects to Lua,
 * but ultimately they are just wrappers.  They pass through all reads and
 * writes to the underlying upb objects.
 *
 * Each upb object lives in a upb arena.  We have a Lua object to wrap the upb
 * arena, but arenas are never exposed to the user.  The Lua arena object just
 * serves to own the upb arena and free it at the proper time, once the Lua GC
 * has determined that there are no more references to anything that lives in
 * that arena.  All wrapper objects strongly reference the arena to which they
 * belong.
 *
 * A global object cache stores a mapping of C pointer (upb_msg*, upb_array*,
 * upb_map*) to a corresponding Lua wrapper.  These references are weak so that
 * the wrappers can be collected if they are no longer needed.  A new wrapper
 * object can always be recreated later.
 *
 *                          +-----+
 *            lupb_arena    |cache|-weak-+
 *                 |  ^     +-----+      |
 *                 |  |                  V
 * Lua level       |  +------------lupb_msg
 * ----------------|-----------------|------------------------------------------
 * upb level       |                 |
 *                 |            +----V------------------------------+
 *                 +->upb_arena | upb_msg  ...(empty arena storage) |
 *                              +-----------------------------------+
 *
 * If the user creates a reference between two objects that have different
 * arenas, we need to fuse the two arenas together, so that the blocks will
 * outlive both arenas.
 *
 *                 +-------------------------->(fused)<----------------+
 *                 |                                                   |
 *                 V                           +-----+                 V
 *            lupb_arena                +-weak-|cache|-weak-+     lupb_arena
 *                 |  ^                 |      +-----+      |        ^   |
 *                 |  |                 V                   V        |   |
 * Lua level       |  +------------lupb_msg              lupb_msg----+   |
 * ----------------|-----------------|-------------------------|---------|------
 * upb level       |                 |                         |         |
 *                 |            +----V----+               +----V----+    V
 *                 +->upb_arena | upb_msg |               | upb_msg | upb_arena
 *                              +------|--+               +--^------+
 *                                     +---------------------+
 * Key invariants:
 *   1. every wrapper references the arena that contains it.
 *   2. every fused arena includes all arenas that own upb objects reachable
 *      from that arena.  In other words, when a wrapper references an arena,
 *      this is sufficient to ensure that any upb object reachable from that
 *      wrapper will stay alive.
 *
 * Additionally, every message object contains a strong reference to the
 * corresponding Descriptor object.  Likewise, array/map objects reference a
 * Descriptor object if they are typed to store message values.
 */

#define LUPB_ARENA "lupb.arena"
#define LUPB_ARRAY "lupb.array"
#define LUPB_MAP "lupb.map"
#define LUPB_MSG "lupb.msg"

#define LUPB_ARENA_INDEX 1
#define LUPB_MSGDEF_INDEX 2  /* For msg, and map/array that store msg */

static void lupb_msg_newmsgwrapper(lua_State *L, int narg, upb_msgval val);
static upb_msg *lupb_msg_check(lua_State *L, int narg);

static upb_fieldtype_t lupb_checkfieldtype(lua_State *L, int narg) {
  uint32_t n = lupb_checkuint32(L, narg);
  bool ok = n >= UPB_TYPE_BOOL && n <= UPB_TYPE_BYTES;
  luaL_argcheck(L, ok, narg, "invalid field type");
  return n;
}

char cache_key;

/* lupb_cacheinit()
 *
 * Creates the global cache used by lupb_cacheget() and lupb_cacheset().
 */
static void lupb_cacheinit(lua_State *L) {
  /* Create our object cache. */
  lua_newtable(L);

  /* Cache metatable gives the cache weak values */
  lua_createtable(L, 0, 1);
  lua_pushstring(L, "v");
  lua_setfield(L, -2, "__mode");
  lua_setmetatable(L, -2);

  /* Set cache in the registry. */
  lua_rawsetp(L, LUA_REGISTRYINDEX, &cache_key);
}

/* lupb_cacheget()
 *
 * Pushes cache[key] and returns true if this key is present in the cache.
 * Otherwise returns false and leaves nothing on the stack.
 */
static bool lupb_cacheget(lua_State *L, const void *key) {
  if (key == NULL) {
    lua_pushnil(L);
    return true;
  }

  lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key);
  lua_rawgetp(L, -1, key);
  if (lua_isnil(L, -1)) {
    lua_pop(L, 2);  /* Pop table, nil. */
    return false;
  } else {
    lua_replace(L, -2);  /* Replace cache table. */
    return true;
  }
}

/* lupb_cacheset()
 *
 * Sets cache[key] = val, where "val" is the value at the top of the stack.
 * Does not pop the value.
 */
static void lupb_cacheset(lua_State *L, const void *key) {
  lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key);
  lua_pushvalue(L, -2);
  lua_rawsetp(L, -2, key);
  lua_pop(L, 1);  /* Pop table. */
}

/* lupb_arena *****************************************************************/

/* lupb_arena only exists to wrap a upb_arena.  It is never exposed to users; it
 * is an internal memory management detail.  Other wrapper objects refer to this
 * object from their userdata to keep the arena-owned data alive.
 */

typedef struct {
  upb_arena *arena;
} lupb_arena;

static upb_arena *lupb_arena_check(lua_State *L, int narg) {
  lupb_arena *a = luaL_checkudata(L, narg, LUPB_ARENA);
  return a->arena;
}

upb_arena *lupb_arena_pushnew(lua_State *L) {
  lupb_arena *a = lupb_newuserdata(L, sizeof(lupb_arena), 1, LUPB_ARENA);
  a->arena = upb_arena_new();
  return a->arena;
}

/**
 * lupb_arena_fuse()
 *
 * Merges |from| into |to| so that there is a single arena group that contains
 * both, and both arenas will point at this new table. */
static void lupb_arena_fuse(lua_State *L, int to, int from) {
  upb_arena *to_arena = lupb_arena_check(L, to);
  upb_arena *from_arena = lupb_arena_check(L, from);
  upb_arena_fuse(to_arena, from_arena);
}

static int lupb_arena_gc(lua_State *L) {
  upb_arena *a = lupb_arena_check(L, 1);
  upb_arena_free(a);
  return 0;
}

static const struct luaL_Reg lupb_arena_mm[] = {
  {"__gc", lupb_arena_gc},
  {NULL, NULL}
};

/* lupb_arenaget()
 *
 * Returns the arena from the given message, array, or map object.
 */
static upb_arena *lupb_arenaget(lua_State *L, int narg) {
  upb_arena *arena;
  lua_getiuservalue(L, narg, LUPB_ARENA_INDEX);
  arena = lupb_arena_check(L, -1);
  lua_pop(L, 1);
  return arena;
}

/* upb <-> Lua type conversion ************************************************/

/* Whether string data should be copied into the containing arena.  We can
 * avoid a copy if the string data is only needed temporarily (like for a map
 * lookup).
 */
typedef enum {
  LUPB_COPY,  /* Copy string data into the arena. */
  LUPB_REF    /* Reference the Lua copy of the string data. */
} lupb_copy_t;

/**
 * lupb_tomsgval()
 *
 * Converts the given Lua value |narg| to a upb_msgval.
 */
static upb_msgval lupb_tomsgval(lua_State *L, upb_fieldtype_t type, int narg,
                                int container, lupb_copy_t copy) {
  upb_msgval ret;
  switch (type) {
    case UPB_TYPE_INT32:
    case UPB_TYPE_ENUM:
      ret.int32_val = lupb_checkint32(L, narg);
      break;
    case UPB_TYPE_INT64:
      ret.int64_val = lupb_checkint64(L, narg);
      break;
    case UPB_TYPE_UINT32:
      ret.uint32_val = lupb_checkuint32(L, narg);
      break;
    case UPB_TYPE_UINT64:
      ret.uint64_val = lupb_checkuint64(L, narg);
      break;
    case UPB_TYPE_DOUBLE:
      ret.double_val = lupb_checkdouble(L, narg);
      break;
    case UPB_TYPE_FLOAT:
      ret.float_val = lupb_checkfloat(L, narg);
      break;
    case UPB_TYPE_BOOL:
      ret.bool_val = lupb_checkbool(L, narg);
      break;
    case UPB_TYPE_STRING:
    case UPB_TYPE_BYTES: {
      size_t len;
      const char *ptr = lupb_checkstring(L, narg, &len);
      switch (copy) {
        case LUPB_COPY: {
          upb_arena *arena = lupb_arenaget(L, container);
          char *data = upb_arena_malloc(arena, len);
          memcpy(data, ptr, len);
          ret.str_val = upb_strview_make(data, len);
          break;
        }
        case LUPB_REF:
          ret.str_val = upb_strview_make(ptr, len);
          break;
      }
      break;
    }
    case UPB_TYPE_MESSAGE:
      ret.msg_val = lupb_msg_check(L, narg);
      /* Typecheck message. */
      lua_getiuservalue(L, container, LUPB_MSGDEF_INDEX);
      lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
      luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch");
      lua_pop(L, 2);
      break;
  }
  return ret;
}

static void lupb_pushmsgval(lua_State *L, int container, upb_fieldtype_t type,
                            upb_msgval val) {
  switch (type) {
    case UPB_TYPE_INT32:
    case UPB_TYPE_ENUM:
      lupb_pushint32(L, val.int32_val);
      return;
    case UPB_TYPE_INT64:
      lupb_pushint64(L, val.int64_val);
      return;
    case UPB_TYPE_UINT32:
      lupb_pushuint32(L, val.uint32_val);
      return;
    case UPB_TYPE_UINT64:
      lupb_pushuint64(L, val.uint64_val);
      return;
    case UPB_TYPE_DOUBLE:
      lua_pushnumber(L, val.double_val);
      return;
    case UPB_TYPE_FLOAT:
      lua_pushnumber(L, val.float_val);
      return;
    case UPB_TYPE_BOOL:
      lua_pushboolean(L, val.bool_val);
      return;
    case UPB_TYPE_STRING:
    case UPB_TYPE_BYTES:
      lua_pushlstring(L, val.str_val.data, val.str_val.size);
      return;
    case UPB_TYPE_MESSAGE:
      assert(container);
      if (!lupb_cacheget(L, val.msg_val)) {
        lupb_msg_newmsgwrapper(L, container, val);
      }
      return;
  }
  LUPB_UNREACHABLE();
}


/* lupb_array *****************************************************************/

typedef struct {
  upb_array *arr;
  upb_fieldtype_t type;
} lupb_array;

static lupb_array *lupb_array_check(lua_State *L, int narg) {
  return luaL_checkudata(L, narg, LUPB_ARRAY);
}

/**
 * lupb_array_checkindex()
 *
 * Checks the array index at Lua stack index |narg| to verify that it is an
 * integer between 1 and |max|, inclusively.  Also corrects it to be zero-based
 * for C.
 */
static int lupb_array_checkindex(lua_State *L, int narg, uint32_t max) {
  uint32_t n = lupb_checkuint32(L, narg);
  luaL_argcheck(L, n != 0 && n <= max, narg, "invalid array index");
  return n - 1;  /* Lua uses 1-based indexing. */
}

/* lupb_array Public API */

/* lupb_array_new():
 *
 * Handles:
 *   Array(upb.TYPE_INT32)
 *   Array(message_type)
 */
static int lupb_array_new(lua_State *L) {
  lupb_array *larray;
  upb_arena *arena;

  if (lua_type(L, 1) == LUA_TNUMBER) {
    upb_fieldtype_t type = lupb_checkfieldtype(L, 1);
    larray = lupb_newuserdata(L, sizeof(*larray), 1, LUPB_ARRAY);
    larray->type = type;
  } else {
    lupb_msgdef_check(L, 1);
    larray = lupb_newuserdata(L, sizeof(*larray), 2, LUPB_ARRAY);
    larray->type = UPB_TYPE_MESSAGE;
    lua_pushvalue(L, 1);
    lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);
  }

  arena = lupb_arena_pushnew(L);
  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);

  larray->arr = upb_array_new(arena, larray->type);
  lupb_cacheset(L, larray->arr);

  return 1;
}

/* lupb_array_newindex():
 *
 * Handles:
 *   array[idx] = val
 *
 * idx can be within the array or one past the end to extend.
 */
static int lupb_array_newindex(lua_State *L) {
  lupb_array *larray = lupb_array_check(L, 1);
  size_t size = upb_array_size(larray->arr);
  uint32_t n = lupb_array_checkindex(L, 2, size + 1);
  upb_msgval msgval = lupb_tomsgval(L, larray->type, 3, 1, LUPB_COPY);

  if (n == size) {
    upb_array_append(larray->arr, msgval, lupb_arenaget(L, 1));
  } else {
    upb_array_set(larray->arr, n, msgval);
  }

  return 0;  /* 1 for chained assignments? */
}

/* lupb_array_index():
 *
 * Handles:
 *   array[idx] -> val
 *
 * idx must be within the array.
 */
static int lupb_array_index(lua_State *L) {
  lupb_array *larray = lupb_array_check(L, 1);
  size_t size = upb_array_size(larray->arr);
  uint32_t n = lupb_array_checkindex(L, 2, size);
  upb_msgval val = upb_array_get(larray->arr, n);

  lupb_pushmsgval(L, 1, larray->type, val);

  return 1;
}

/* lupb_array_len():
 *
 * Handles:
 *   #array -> len
 */
static int lupb_array_len(lua_State *L) {
  lupb_array *larray = lupb_array_check(L, 1);
  lua_pushnumber(L, upb_array_size(larray->arr));
  return 1;
}

static const struct luaL_Reg lupb_array_mm[] = {
  {"__index", lupb_array_index},
  {"__len", lupb_array_len},
  {"__newindex", lupb_array_newindex},
  {NULL, NULL}
};


/* lupb_map *******************************************************************/

typedef struct {
  upb_map *map;
  upb_fieldtype_t key_type;
  upb_fieldtype_t value_type;
} lupb_map;

#define MAP_MSGDEF_INDEX 1

static lupb_map *lupb_map_check(lua_State *L, int narg) {
  return luaL_checkudata(L, narg, LUPB_MAP);
}

/* lupb_map Public API */

/**
 * lupb_map_new
 *
 * Handles:
 *   new_map = upb.Map(key_type, value_type)
 *   new_map = upb.Map(key_type, value_msgdef)
 */
static int lupb_map_new(lua_State *L) {
  upb_arena *arena;
  lupb_map *lmap;

  if (lua_type(L, 2) == LUA_TNUMBER) {
    lmap = lupb_newuserdata(L, sizeof(*lmap), 1, LUPB_MAP);
    lmap->value_type = lupb_checkfieldtype(L, 2);
  } else {
    lupb_msgdef_check(L, 2);
    lmap = lupb_newuserdata(L, sizeof(*lmap), 2, LUPB_MAP);
    lmap->value_type = UPB_TYPE_MESSAGE;
    lua_pushvalue(L, 2);
    lua_setiuservalue(L, -2, MAP_MSGDEF_INDEX);
  }

  arena = lupb_arena_pushnew(L);
  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);

  lmap->key_type = lupb_checkfieldtype(L, 1);
  lmap->map = upb_map_new(arena, lmap->key_type, lmap->value_type);
  lupb_cacheset(L, lmap->map);

  return 1;
}

/**
 * lupb_map_index
 *
 * Handles:
 *   map[key]
 */
static int lupb_map_index(lua_State *L) {
  lupb_map *lmap = lupb_map_check(L, 1);
  upb_msgval key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF);
  upb_msgval val;

  if (upb_map_get(lmap->map, key, &val)) {
    lupb_pushmsgval(L, 1, lmap->value_type, val);
  } else {
    lua_pushnil(L);
  }

  return 1;
}

/**
 * lupb_map_len
 *
 * Handles:
 *   map_len = #map
 */
static int lupb_map_len(lua_State *L) {
  lupb_map *lmap = lupb_map_check(L, 1);
  lua_pushnumber(L, upb_map_size(lmap->map));
  return 1;
}

/**
 * lupb_map_newindex
 *
 * Handles:
 *   map[key] = val
 *   map[key] = nil  # to remove from map
 */
static int lupb_map_newindex(lua_State *L) {
  lupb_map *lmap = lupb_map_check(L, 1);
  upb_map *map = lmap->map;
  upb_msgval key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF);

  if (lua_isnil(L, 3)) {
    upb_map_delete(map, key);
  } else {
    upb_msgval val = lupb_tomsgval(L, lmap->value_type, 3, 1, LUPB_COPY);
    upb_map_set(map, key, val, lupb_arenaget(L, 1));
  }

  return 0;
}

static int lupb_mapiter_next(lua_State *L) {
  int map = lua_upvalueindex(2);
  size_t *iter = lua_touserdata(L, lua_upvalueindex(1));
  lupb_map *lmap = lupb_map_check(L, map);

  if (upb_mapiter_next(lmap->map, iter)) {
    upb_msgval key = upb_mapiter_key(lmap->map, *iter);
    upb_msgval val = upb_mapiter_value(lmap->map, *iter);
    lupb_pushmsgval(L, map, lmap->key_type, key);
    lupb_pushmsgval(L, map, lmap->value_type, val);
    return 2;
  } else {
    return 0;
  }

}

/**
 * lupb_map_pairs()
 *
 * Handles:
 *   pairs(map)
 */
static int lupb_map_pairs(lua_State *L) {
  lupb_map_check(L, 1);
  size_t *iter = lua_newuserdata(L, sizeof(*iter));

  *iter = UPB_MAP_BEGIN;
  lua_pushvalue(L, 1);

  /* Upvalues are [iter, lupb_map]. */
  lua_pushcclosure(L, &lupb_mapiter_next, 2);

  return 1;
}

/* upb_mapiter ]]] */

static const struct luaL_Reg lupb_map_mm[] = {
  {"__index", lupb_map_index},
  {"__len", lupb_map_len},
  {"__newindex", lupb_map_newindex},
  {"__pairs", lupb_map_pairs},
  {NULL, NULL}
};


/* lupb_msg *******************************************************************/

typedef struct {
  upb_msg *msg;
} lupb_msg;

/* lupb_msg helpers */

static upb_msg *lupb_msg_check(lua_State *L, int narg) {
  lupb_msg *msg = luaL_checkudata(L, narg, LUPB_MSG);
  return msg->msg;
}

static const upb_fielddef *lupb_msg_checkfield(lua_State *L, int msg,
                                               int field) {
  size_t len;
  const char *fieldname = luaL_checklstring(L, field, &len);
  const upb_msgdef *m;
  const upb_fielddef *f;

  lua_getiuservalue(L, msg, LUPB_MSGDEF_INDEX);
  m = lupb_msgdef_check(L, -1);
  f = upb_msgdef_ntof(m, fieldname, len);
  if (f == NULL) {
    luaL_error(L, "no such field '%s'", fieldname);
  }
  lua_pop(L, 1);

  return f;
}

/**
 * lupb_msg_newmsgwrapper()
 *
 * Creates a new wrapper for a message, copying the arena and msgdef references
 * from |narg| (which should be an array or map).
 */
static void lupb_msg_newmsgwrapper(lua_State *L, int narg, upb_msgval val) {
  lupb_msg *lmsg = lupb_newuserdata(L, sizeof(*lmsg), 2, LUPB_MSG);
  lmsg->msg = (upb_msg*)val.msg_val;  /* XXX: cast isn't great. */
  lupb_cacheset(L, lmsg->msg);

  /* Copy both arena and msgdef into the wrapper. */
  lua_getiuservalue(L, narg, LUPB_ARENA_INDEX);
  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);
  lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
  lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);
}

/**
 * lupb_msg_newud()
 *
 * Creates the Lua userdata for a new wrapper object, adding a reference to
 * the msgdef if necessary.
 */
static void *lupb_msg_newud(lua_State *L, int narg, size_t size,
                            const char *type, const upb_fielddef *f) {
  if (upb_fielddef_type(f) == UPB_TYPE_MESSAGE) {
    /* Wrapper needs a reference to the msgdef. */
    void* ud = lupb_newuserdata(L, size, 2, type);
    lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
    lupb_msgdef_pushsubmsgdef(L, f);
    lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);
    return ud;
  } else {
    return lupb_newuserdata(L, size, 1, type);
  }
}

/**
 * lupb_msg_newwrapper()
 *
 * Creates a new Lua wrapper object to wrap the given array, map, or message.
 */
static void lupb_msg_newwrapper(lua_State *L, int narg, const upb_fielddef *f,
                                upb_mutmsgval val) {
  if (upb_fielddef_ismap(f)) {
    const upb_msgdef *entry = upb_fielddef_msgsubdef(f);
    const upb_fielddef *key_f = upb_msgdef_itof(entry, UPB_MAPENTRY_KEY);
    const upb_fielddef *val_f = upb_msgdef_itof(entry, UPB_MAPENTRY_VALUE);
    lupb_map *lmap = lupb_msg_newud(L, narg, sizeof(*lmap), LUPB_MAP, val_f);
    lmap->key_type = upb_fielddef_type(key_f);
    lmap->value_type = upb_fielddef_type(val_f);
    lmap->map = val.map;
  } else if (upb_fielddef_isseq(f)) {
    lupb_array *larr = lupb_msg_newud(L, narg, sizeof(*larr), LUPB_ARRAY, f);
    larr->type = upb_fielddef_type(f);
    larr->arr = val.array;
  } else {
    lupb_msg *lmsg = lupb_msg_newud(L, narg, sizeof(*lmsg), LUPB_MSG, f);
    lmsg->msg = val.msg;
  }

  /* Copy arena ref to new wrapper.  This may be a different arena than the
   * underlying data was originally constructed from, but if so both arenas
   * must be in the same group. */
  lua_getiuservalue(L, narg, LUPB_ARENA_INDEX);
  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);

  lupb_cacheset(L, val.msg);
}

/**
 * lupb_msg_typechecksubmsg()
 *
 * Typechecks the given array, map, or msg against this upb_fielddef.
 */
static void lupb_msg_typechecksubmsg(lua_State *L, int narg, int msgarg,
                                     const upb_fielddef *f) {
  /* Typecheck this map's msgdef against this message field. */
  lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX);
  lua_getiuservalue(L, msgarg, LUPB_MSGDEF_INDEX);
  lupb_msgdef_pushsubmsgdef(L, f);
  luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch");
  lua_pop(L, 2);
}

/* lupb_msg Public API */

/**
 * lupb_msg_pushnew
 *
 * Handles:
 *   new_msg = MessageClass()
 *   new_msg = MessageClass{foo = "bar", baz = 3, quux = {foo = 3}}
 */
int lupb_msg_pushnew(lua_State *L) {
  int argcount = lua_gettop(L);
  const upb_msgdef *m = lupb_msgdef_check(L, 1);
  lupb_msg *lmsg = lupb_newuserdata(L, sizeof(lupb_msg), 2, LUPB_MSG);
  upb_arena *arena = lupb_arena_pushnew(L);

  lua_setiuservalue(L, -2, LUPB_ARENA_INDEX);
  lua_pushvalue(L, 1);
  lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX);

  lmsg->msg = upb_msg_new(m, arena);
  lupb_cacheset(L, lmsg->msg);

  if (argcount > 1) {
    /* Set initial fields from table. */
    int msg = lua_gettop(L);
    lua_pushnil(L);
    while (lua_next(L, 2) != 0) {
      lua_pushvalue(L, -2);  /* now stack is key, val, key */
      lua_insert(L, -3);  /* now stack is key, key, val */
      lua_settable(L, msg);
    }
  }

  return 1;
}

/**
 * lupb_msg_index
 *
 * Handles:
 *   msg.foo
 *   msg["foo"]
 *   msg[field_descriptor]  # (for extensions) (TODO)
 */
static int lupb_msg_index(lua_State *L) {
  upb_msg *msg = lupb_msg_check(L, 1);
  const upb_fielddef *f = lupb_msg_checkfield(L, 1, 2);

  if (upb_fielddef_isseq(f) || upb_fielddef_issubmsg(f)) {
    /* Wrapped type; get or create wrapper. */
    upb_arena *arena = upb_fielddef_isseq(f) ? lupb_arenaget(L, 1) : NULL;
    upb_mutmsgval val = upb_msg_mutable(msg, f, arena);
    if (!lupb_cacheget(L, val.msg)) {
      lupb_msg_newwrapper(L, 1, f, val);
    }
  } else {
    /* Value type, just push value and return .*/
    upb_msgval val = upb_msg_get(msg, f);
    lupb_pushmsgval(L, 0, upb_fielddef_type(f), val);
  }

  return 1;
}

/**
 * lupb_msg_newindex()
 *
 * Handles:
 *   msg.foo = bar
 *   msg["foo"] = bar
 *   msg[field_descriptor] = bar  # (for extensions) (TODO)
 */
static int lupb_msg_newindex(lua_State *L) {
  upb_msg *msg = lupb_msg_check(L, 1);
  const upb_fielddef *f = lupb_msg_checkfield(L, 1, 2);
  upb_msgval msgval;
  bool merge_arenas = true;

  if (upb_fielddef_ismap(f)) {
    lupb_map *lmap = lupb_map_check(L, 3);
    const upb_msgdef *entry = upb_fielddef_msgsubdef(f);
    const upb_fielddef *key_f = upb_msgdef_itof(entry, UPB_MAPENTRY_KEY);
    const upb_fielddef *val_f = upb_msgdef_itof(entry, UPB_MAPENTRY_VALUE);
    upb_fieldtype_t key_type = upb_fielddef_type(key_f);
    upb_fieldtype_t value_type = upb_fielddef_type(val_f);
    luaL_argcheck(L, lmap->key_type == key_type, 3, "key type mismatch");
    luaL_argcheck(L, lmap->value_type == value_type, 3, "value type mismatch");
    if (value_type == UPB_TYPE_MESSAGE) {
      lupb_msg_typechecksubmsg(L, 3, 1, val_f);
    }
    msgval.map_val = lmap->map;
  } else if (upb_fielddef_isseq(f)) {
    lupb_array *larr = lupb_array_check(L, 3);
    upb_fieldtype_t type = upb_fielddef_type(f);
    luaL_argcheck(L, larr->type == type, 3, "array type mismatch");
    if (type == UPB_TYPE_MESSAGE) {
      lupb_msg_typechecksubmsg(L, 3, 1, f);
    }
    msgval.array_val = larr->arr;
  } else if (upb_fielddef_issubmsg(f)) {
    upb_msg *msg = lupb_msg_check(L, 3);
    lupb_msg_typechecksubmsg(L, 3, 1, f);
    msgval.msg_val = msg;
  } else {
    msgval = lupb_tomsgval(L, upb_fielddef_type(f), 3, 1, LUPB_COPY);
    merge_arenas = false;
  }

  if (merge_arenas) {
    lua_getiuservalue(L, 1, LUPB_ARENA_INDEX);
    lua_getiuservalue(L, 3, LUPB_ARENA_INDEX);
    lupb_arena_fuse(L, lua_absindex(L, -2), lua_absindex(L, -1));
    lua_pop(L, 2);
  }

  upb_msg_set(msg, f, msgval, lupb_arenaget(L, 1));

  /* Return the new value for chained assignments. */
  lua_pushvalue(L, 3);
  return 1;
}

/**
 * lupb_msg_tostring()
 *
 * Handles:
 *   tostring(msg)
 *   print(msg)
 *   etc.
 */
static int lupb_msg_tostring(lua_State *L) {
  upb_msg *msg = lupb_msg_check(L, 1);
  const upb_msgdef *m;
  char buf[1024];
  size_t size;

  lua_getiuservalue(L, 1, LUPB_MSGDEF_INDEX);
  m = lupb_msgdef_check(L, -1);

  size = upb_text_encode(msg, m, NULL, 0, buf, sizeof(buf));

  if (size < sizeof(buf)) {
    lua_pushlstring(L, buf, size);
  } else {
    char *ptr = malloc(size + 1);
    upb_text_encode(msg, m, NULL, 0, ptr, size + 1);
    lua_pushlstring(L, ptr, size);
    free(ptr);
  }

  return 1;
}

static const struct luaL_Reg lupb_msg_mm[] = {
  {"__index", lupb_msg_index},
  {"__newindex", lupb_msg_newindex},
  {"__tostring", lupb_msg_tostring},
  {NULL, NULL}
};


/* lupb_msg toplevel **********************************************************/

/**
 * lupb_decode()
 *
 * Handles:
 *   msg = upb.decode(MessageClass, bin_string)
 */
static int lupb_decode(lua_State *L) {
  size_t len;
  const upb_msgdef *m = lupb_msgdef_check(L, 1);
  const char *pb = lua_tolstring(L, 2, &len);
  const upb_msglayout *layout = upb_msgdef_layout(m);
  char *buf;
  upb_msg *msg;
  upb_arena *arena;
  bool ok;

  /* Create message. */
  lua_pushcfunction(L, &lupb_msg_pushnew);
  lua_pushvalue(L, 1);
  lua_call(L, 1, 1);
  msg = lupb_msg_check(L, -1);

  lua_getiuservalue(L, -1, LUPB_ARENA_INDEX);
  arena = lupb_arena_check(L, -1);
  lua_pop(L, 1);

  /* Copy input data to arena, message will reference it. */
  buf = upb_arena_malloc(arena, len);
  memcpy(buf, pb, len);

  ok = upb_decode(buf, len, msg, layout, arena);

  if (!ok) {
    lua_pushstring(L, "Error decoding protobuf.");
    return lua_error(L);
  }

  return 1;
}

/**
 * lupb_encode()
 *
 * Handles:
 *   bin_string = upb.encode(msg)
 */
static int lupb_encode(lua_State *L) {
  const upb_msg *msg = lupb_msg_check(L, 1);
  const upb_msglayout *layout;
  upb_arena *arena = lupb_arena_pushnew(L);
  size_t size;
  char *result;

  lua_getiuservalue(L, 1, LUPB_MSGDEF_INDEX);
  layout = upb_msgdef_layout(lupb_msgdef_check(L, -1));
  lua_pop(L, 1);

  result = upb_encode(msg, (const void*)layout, arena, &size);

  if (!result) {
    lua_pushstring(L, "Error encoding protobuf.");
    return lua_error(L);
  }

  lua_pushlstring(L, result, size);

  return 1;
}

static const struct luaL_Reg lupb_msg_toplevel_m[] = {
  {"Array", lupb_array_new},
  {"Map", lupb_map_new},
  {"decode", lupb_decode},
  {"encode", lupb_encode},
  {NULL, NULL}
};

void lupb_msg_registertypes(lua_State *L) {
  lupb_setfuncs(L, lupb_msg_toplevel_m);

  lupb_register_type(L, LUPB_ARENA, NULL, lupb_arena_mm);
  lupb_register_type(L, LUPB_ARRAY, NULL, lupb_array_mm);
  lupb_register_type(L, LUPB_MAP,   NULL, lupb_map_mm);
  lupb_register_type(L, LUPB_MSG,   NULL, lupb_msg_mm);

  lupb_cacheinit(L);
}
