-- gall.object
--
-- Git Abstraction Layer for Lua -- Generic object interface
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--
--

---
-- Generic object interface
--
-- @module gall.object

local ll = require "gall.ll"
local commit = require "gall.commit"
local tree = require "gall.tree"
local tag = require "gall.tag"

local repos = setmetatable({}, {__mode="k"})
local blobs = setmetatable({}, {__mode="k"})

local known_types = {
   blob = true, commit = true, tree = true, tag = true,
}

local function _objectindex(obj, field)
   local ok, ret
   local blob = blobs[obj]
   if field == "type" then
      if blob then
	 ok, ret = 0, ll.git2.get_object_type(blob)
      else
	 ok, ret = repos[obj]:gather("cat-file", "-t", obj.sha)
      end
   elseif field == "size" then
      if blob then
	 ok, ret = 0, ll.git2.get_object_size(blob)
      else
	 ok, ret = repos[obj]:gather("cat-file", "-s", obj.sha)
      end
   elseif field == "raw" then
      if blob and obj.type ~= "tree" then
	 ok, ret = 0, ll.git2.get_object_raw(blob)
      else
	 ok, ret = repos[obj]:rawgather("cat-file", (obj.type == "tag" and "tag" or "-p"), obj.sha)
      end
   elseif field == "content" then
      assert(known_types[obj.type], "Unknown type: " .. obj.type)
      if obj.type == "blob" then
	 ok, ret = 0, obj.raw
      elseif obj.type == "commit" then
	 ok, ret = 0, commit.new(repos[obj], obj)
      elseif obj.type == "tree" then
	 ok, ret = 0, tree.new(repos[obj], obj)
      elseif obj.type == "tag" then
	 ok, ret = 0, tag.new(repos[obj], obj)
      end
   else
      error("Unknown field <" .. tostring(field) .. ">")
   end

   assert(ok == 0, "Unable to retrieve " .. field)

   if field == "size" then ret = tonumber(ret) end

   rawset(obj, field, ret)

   return ret
end

function _objecttostring(obj)
   return "<GitObject(" .. tostring(obj.sha)..") in " .. tostring(repos[obj]) .. ">"
end

local objectmeta = {
   __index = _objectindex,
   __tostring = _objecttostring
}

---
-- Low level git object
--
-- @type object

---
-- The type of the object
--
-- @field type

---
-- The size of the object
--
-- @field size

---
-- The object's raw content
--
-- @field raw

---
-- The object's processed content.
--
-- * If the object is a commit, then this will be a @{commit} instance.
-- * If the object is a tree, then this will be a @{tree} instance.
-- * If the object is a tag, then this will be a @{tag} instance.
--
-- @field content

---
-- The object's OID (SHA1)
--
-- @field sha

--- @section end

---
-- Create a new raw object.
--
-- Create a new instance of @{object} referring to the given SHA1 OID.
--
-- @function new
-- @tparam repository repo The repository within which the object resides.
-- @tparam string sha The SHA1 OID of the object in the repository.
-- @treturn object The object instance encapsulating the given object.

local function _new(repo, sha)
   local ret = setmetatable({sha=sha}, objectmeta)
   repos[ret] = repo
   if ll.git2 then
      blobs[ret] = ll.git2.get_object(repo.git2.repo, sha)
   end
   return ret
end

---
-- Create a new raw object.
--
-- Create a new raw object and insert it into the given repository.  This will
-- not only create the @{object} instance but also insert it into the
-- repository storage.
--
-- @function create
-- @tparam repository repo The repository within which the object resides.
-- @tparam string type The type of the object (e.g. blob or commit)
-- @tparam string content The content to insert as the object's content.
-- @treturn[1] object The object instance encapsulating the given object.
-- @treturn[2] nil Nil on error
-- @treturn[2] string The error message

local function _create(repo, type, content)
   local why, sha = 
      repo:_run_with_input(content, ll.chomp, 
			   "hash-object", "-t", type, "-w", "--stdin")
   if why ~= 0 then
      return nil, "hash-object returned " .. tostring(why)
   end

   return _new(repo, sha)
end

return {
   new = _new,
   create = _create,
}
