--- Wraps the low-level threads object.
--
-- @module llthreads2.ex

--
-- Note! Define this function prior all `local` definitions
--       to prevent use upvalue by accident
--
local bootstrap_code = require"string".dump(function(lua_init, prelude, code, ...)
  local loadstring = loadstring or load
  local unpack     = table.unpack or unpack

  local function load_src(str)
    local f, n
    if str:sub(1,1) == '@' then
      n = str:sub(2)
      f = assert(loadfile(n))
    else
      n = '=(loadstring)'
      f = assert(loadstring(str))
    end
    return f, n
  end

  local function pack_n(...)
    return { n = select("#", ...), ... }
  end

  local function unpack_n(t)
    return unpack(t, 1, t.n)
  end

  if lua_init and #lua_init > 0 then
    local init = load_src(lua_init)
    init()
  end

  local args

  if prelude and #prelude > 0 then
    prelude = load_src(prelude)
    args = pack_n(prelude(...))
  else
    args = pack_n(...)
  end

  local func
  func, args[0] = load_src(code)

  _G.arg = args
     arg = args

  return func(unpack_n(args))
end)

local ok, llthreads = pcall(require, "llthreads2")
if not ok then llthreads = require"llthreads" end

local os        = require"os"
local string    = require"string"
local table     = require"table"

local setmetatable, tonumber, assert = setmetatable, tonumber, assert

-------------------------------------------------------------------------------
local LUA_INIT = "LUA_INIT" do

local lua_version_t
local function lua_version()
  if not lua_version_t then 
    local version = assert(_G._VERSION)
    local maj,min = version:match("^Lua (%d+)%.(%d+)$")
    if maj then                         lua_version_t = {tonumber(maj),tonumber(min)}
    elseif not math.mod then            lua_version_t = {5,2}
    elseif table.pack and not pack then lua_version_t = {5,2}
    else                                lua_version_t = {5,2} end
  end
  return lua_version_t[1], lua_version_t[2]
end

local LUA_MAJOR, LUA_MINOR = lua_version()
local IS_LUA_51 = (LUA_MAJOR == 5) and (LUA_MINOR == 1)

local LUA_INIT_VER
if not IS_LUA_51 then
  LUA_INIT_VER = LUA_INIT .. "_" .. LUA_MAJOR .. "_" .. LUA_MINOR
end

LUA_INIT = LUA_INIT_VER and os.getenv( LUA_INIT_VER ) or os.getenv( LUA_INIT ) or ""

end
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
local thread_mt = {} do
thread_mt.__index = thread_mt

--- Thread object.
--
-- @type thread

--- Start thread.
--
-- @tparam ?boolean detached
-- @tparam ?boolean joinable
-- @return self
function thread_mt:start(...)
  local ok, err = self.thread:start(...)
  if not ok then return nil, err end
  return self
end

--- Join thread.
--
-- @tparam ?number timeout Windows suppurts arbitrary value, but POSIX supports only 0
function thread_mt:join(...)
  return self.thread:join(...)
end

--- Check if thread still working.
-- You can call `join` to get returned values if thiread is not alive.
function thread_mt:alive()
  return self.thread:alive()
end

--- Check if thread was started.
-- 
function thread_mt:started()
  return self.thread:started()
end

--- Check if thread is detached.
-- This function returns valid value only for started thread.
function thread_mt:detached()
  return self.thread:detached()
end

--- Check if thread is joinable.
-- This function returns valid value only for started thread.
function thread_mt:joinable()
  return self.thread:joinable()
end

--- Interrupt thread
-- The thread is interrupted by installing a debug hook that
-- creates an error.
-- @tparam ?boolean if not given, interrupt only once,
--         otherwise this arg sets or unsets permanent interrupt.
function thread_mt:interrupt(arg)
  return self.thread:interrupt(arg)
end

end
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
local threads = {} do

local function new_thread(lua_init, prelude, code, ...)
  if type(lua_init) == "function" then
    lua_init = string.dump(lua_init)
  end

  if type(prelude) == "function" then
    prelude = string.dump(prelude)
  end

  if type(code) == "function" then
    code = string.dump(code)
  end

  local thread = llthreads.new(bootstrap_code, lua_init, prelude, code, ...)
  return setmetatable({
    thread = thread,
  }, thread_mt)
end

--- Create new thread object
-- 
-- @tparam string|function|THREAD_OPTIONS source thread source code.
--
threads.new = function (code, ...)
  assert(code)

  if type(code) == "table" then
    local source = assert(code.source or code[1])
    local init = (code.lua_init == nil) and LUA_INIT or code.lua_init
    return new_thread(init, code.prelude, source, ...)
  end

  return new_thread(LUA_INIT, nil, code, ...)
end

end
-------------------------------------------------------------------------------

--- A table describe threads constructor options.
--
-- @tfield string|function source thread source code (or first value of table)
-- @tfield ?string|function prelude thread prelude code. This code can change thread arguments.
--  e.g. it can remove some values or change their type.
-- @lua_init ?string|function|false by default child lua state try use LUA_INIT environment variable
--  just like regular lua interpretator.
--
-- @table THREAD_OPTIONS

return threads