int Lua_print(lua_State *L) {
    Scratch scratch;
    int nargs = lua_gettop(L);
    View *null_view = GetView(NullViewID);
    for (int i = 1; i <= nargs; i += 1) {
        String string = lua_tostring(L, i);
        Appendf(null_view, "%S ", string);
    }
    Appendf(null_view, "\n");
    lua_pop(L, nargs);
    return 0;
}

int Lua_Print(lua_State *L) {
    Scratch scratch;
    int nargs = lua_gettop(L);
    for (int i = 1; i <= nargs; i += 1) {
        String string = lua_tostring(L, i);
        Appendf(TraceView, "%S ", string);
    }
    Appendf(TraceView, "\n");
    lua_pop(L, nargs);
    return 0;
}

int Lua_GetLoadWord(lua_State *L) {
    BSet active = GetBSet(ActiveWindowID);
    Range range  = active.view->carets[0].range;
    if (GetSize(range) == 0) {
        range = EncloseLoadWord(active.buffer, range.min);
    }

    Scratch scratch;
    String  string = AllocCharString(scratch, active.buffer, range);
    lua_pushlstring(L, string.data, string.len);
    return 1;
}

int Lua_BufferExists(lua_State *L) {
    String string = lua_tostring(L, 1);
    lua_pop(L, 1);
    Buffer *buffer = GetBuffer(string);
    lua_pushboolean(L, buffer != NULL);
    return 1;
}

int Lua_GetSelection(lua_State *L) {
    Scratch scratch;
    BSet main = GetBSet(LastActiveLayoutWindowID);
    String16 string16 = GetString(main.buffer, main.view->carets[0].range);
    String string = ToString(scratch, string16);
    lua_pushlstring(L, string.data, string.len);
    return 1;
}

int Lua_GetEntireBuffer(lua_State *L) {
    Scratch scratch;
    BSet main = GetBSet(LastActiveLayoutWindowID);
    String16 string16 = GetString(main.buffer);
    String string = ToString(scratch, string16);
    lua_pushlstring(L, string.data, string.len);
    return 1;
}

int Lua_GetClipboard(lua_State *L) {
    Scratch scratch;
    String string = ToString(scratch, SavedClipboardString);
    lua_pushlstring(L, string.data, string.len);
    return 1;
}

int Lua_GetFilename(lua_State *L) {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    lua_pushlstring(L, main.buffer->name.data, main.buffer->name.len);
    return 1;
}

int Lua_GetLine(lua_State *L) {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    Caret caret = main.view->carets[0];
    Int front = GetFront(caret);
    Int line = PosToLine(main.buffer, front);
    lua_pushinteger(L, line + 1);
    return 1;
}

int Lua_FileExists(lua_State *L) {
    String path = luaL_checkstring(L, 1);
    lua_pop(L, 1);
    bool exists = FileExists(path);
    lua_pushboolean(L, exists);
    return 1;
}

int Lua_GetWorkDir(lua_State *L) {
    lua_pushlstring(L, WorkDir.data, WorkDir.len);
    return 1;
}

int Lua_GetExeDir(lua_State *L) {
    Scratch scratch;
    String exe_dir = GetExeDir(scratch);
    lua_pushlstring(L, exe_dir.data, exe_dir.len);
    return 1;
}

int Lua_GetMainDir(lua_State *L) {
    String name = GetMainDir();
    lua_pushlstring(L, name.data, name.len);
    return 1;
}

struct OnOpenResult {
    String kind;
    String file_path;
    Int line, col;
    String working_dir;
    String cmd;
};

OnOpenResult CallOnOpen(Allocator allocator, String path, String meta) {
    lua_getglobal(LuaState, "OnOpen");
    lua_pushlstring(LuaState, path.data, path.len);
    lua_pushlstring(LuaState, meta.data, meta.len);
    if (!CallLuaFunc("OnOpen", 2, 1)) {
        return {};
    }

    String file_path   = GetFieldString(LuaState, "file_path");
    String line_string = GetFieldString(LuaState, "line");
    String col_string  = GetFieldString(LuaState, "col");
    String cmd         = GetFieldString(LuaState, "cmd");
    String working_dir = GetFieldString(LuaState, "working_dir");
    String kind        = GetFieldString(LuaState, "kind");

    OnOpenResult result = {};
    result.cmd = cmd;
    result.working_dir = working_dir;
    result.file_path = file_path;
    if (!IsAbsolute(result.file_path)) {
        String dir = GetMainDir();
        result.file_path = Format(allocator, "%S/%S", dir, result.file_path);
    }
    if (col_string.len) {
        result.col = strtoll(col_string.data, NULL, 10);
    } else {
        result.col = -1;
    }
    if (line_string.len) {
        result.line = strtoll(line_string.data, NULL, 10);
    } else {
        result.line = -1;
    }
    result.kind = kind;


    return result;
}

bool CallIsCode(String path, String meta = "") {
    lua_getglobal(LuaState, "IsCode");
    lua_pushlstring(LuaState, path.data, path.len);
    lua_pushlstring(LuaState, meta.data, meta.len);
    if (!CallLuaFunc("IsCode", 2, 1)) {
        return false;
    }

    bool result = lua_toboolean(LuaState, -1);
    lua_pop(LuaState, 1);
    return result;
}

void CallOnSave(BufferID buffer_id) {
    lua_getglobal(LuaState, "OnSave");
    lua_pushinteger(LuaState, buffer_id.id);
    CallLuaFunc("OnSave", 1, 0);
}

bool CallOnCommand(Event *event) {
    lua_getglobal(LuaState, "OnCommand");
    PushEvent(LuaState, event);
    CallLuaFunc("OnCommand", 1, 1);
    bool result = lua_toboolean(LuaState, -1);
    lua_pop(LuaState, 1);
    return result;
}

void CallLuaOnUpdate(Event *event) {
    lua_getglobal(LuaState, "OnUpdate");
    PushEvent(LuaState, event);
    CallLuaFunc("OnUpdate", 1, 0);
}
