--------------------------------------------------------------------------------
------------------------------  #####      ##     ------------------------------
------------------------------ ##   ##  #  ##     ------------------------------
------------------------------ ##   ## ##  ##     ------------------------------
------------------------------ ##   ##  #  ##     ------------------------------
------------------------------  #####  ### ###### ------------------------------
--------------------------------                --------------------------------
----------------------- An Object Request Broker in Lua ------------------------
--------------------------------------------------------------------------------
-- Project: OiL - ORB in Lua: An Object Request Broker in Lua                 --
-- Release: 0.5                                                               --
-- Title  : Interface Definition Language (IDL) specifications in Lua         --
-- Authors: Renato Maia <maia@inf.puc-rio.br>                                 --
--------------------------------------------------------------------------------
-- Interface:                                                                 --
--   istype(object)        Checks whether object is an IDL type               --
--   isspec(object)        Checks whether object is an IDL specification      --
--                                                                            --
--   null                  IDL null type                                      --
--   void                  IDL void type                                      --
--   short                 IDL integer type short                             --
--   long                  IDL integer type long                              --
--   ushort                IDL integer type unsigned short                    --
--   ulong                 IDL integer type unsigned long                     --
--   float                 IDL floating-point numeric type                    --
--   double                IDL double-precision floating-point numeric type   --
--   boolean               IDL boolean type                                   --
--   char                  IDL character type                                 --
--   octet                 IDL raw byte type                                  --
--   any                   IDL generic type                                   --
--   TypeCode              IDL meta-type                                      --
--   string                IDL string type                                    --
--                                                                            --
--   Object(definition)    IDL Object type construtor                         --
--   struct(definition)    IDL struct type construtor                         --
--   union(definition)     IDL union type construtor                          --
--   enum(definition)      IDL enumeration type construtor                    --
--   sequence(definition)  IDL sequence type construtor                       --
--   array(definition)     IDL array type construtor                          --
--   valuetype(definition) IDL value type construtor                           --
--   typedef(definition)   IDL type definition construtor                     --
--   except(definition)    IDL expection construtor                           --
--                                                                            --
--   attribute(definition) IDL attribute construtor                           --
--   operation(definition) IDL operation construtor                           --
--   module(definition)    IDL module structure constructor                   --
--   interface(definition) IDL object interface structure constructor         --
--                                                                            --
--   OctetSequence         IDL type used in OiL implementation                --
--   Version               IDL type used in OiL implementation                --
--                                                                            --
--   ScopeMemberList       Class that defines behavior of interface member list-
--------------------------------------------------------------------------------
-- Notes:                                                                     --
--   The syntax used for description of IDL specifications is strongly based  --
--   on the work provided by Letícia Nogeira (i.e. LuaRep), which was mainly  --
--   inteded to provide an user-friendly syntax. This approach may change to  --
--   allow better fitting into CORBA model, since the use of LuaIDL parsing   --
--   facilities already provides an user-friendly way to define IDL           --
--   specifications. However backward compatibility may be provided whenever  --
--   possible.                                                                --
--------------------------------------------------------------------------------

local type     = type
local newproxy = newproxy
local pairs    = pairs
local ipairs   = ipairs
local rawset   = rawset
local require  = require
local rawget   = rawget

-- backup of string package functions to avoid name crash with string IDL type
local match  = require("string").match
local format = require("string").format
local table  = require "table"

local OrderedSet        = require "loop.collection.OrderedSet"
local UnorderedArraySet = require "loop.collection.UnorderedArraySet"

local oo     = require "oil.oo"
local assert = require "oil.assert"

module "oil.corba.idl"                                                          --[[VERBOSE]] local verbose = require "oil.verbose"

--------------------------------------------------------------------------------
-- IDL element types -----------------------------------------------------------

BasicTypes = {
	null       = true,
	void       = true,
	short      = true,
	long       = true,
	longlong   = true,
	ushort     = true,
	ulong      = true,
	ulonglong  = true,
	float      = true,
	double     = true,
	longdouble = true,
	boolean    = true,
	char       = true,
	octet      = true,
	any        = true,
	TypeCode   = true,
}

UserTypes = {
	string    = true,
	Object    = true,
	struct    = true,
	union     = true,
	enum      = true,
	sequence  = true,
	array     = true,
	valuetype = true,
	valuebox  = true,
	typedef   = true,
	except    = true,
	interface = true,
	abstract_interface = true,
	local_interface = true,
}

InterfaceElements = {
	attribute = true,
	operation = true,
	module    = true,
}

--------------------------------------------------------------------------------
-- Auxilary module functions ---------------------------------------------------

function istype(object)
	return type(object) == "table" and (
	       	BasicTypes[object._type] == object or
	       	UserTypes[object._type]
	       )
end

function isspec(object)
	return type(object) == "table" and (
	       	BasicTypes[object._type] == object or
	       	UserTypes[object._type] or
	       	InterfaceElements[object._type]
	       )
end

assert.TypeCheckers["idl type"]    = istype
assert.TypeCheckers["idl def."]    = isspec
assert.TypeCheckers["^idl (%l+)$"] = function(value, name)
	if istype(value)
		then return (value._type == name), ("idl "..name.." type")
		else return false, name
	end
end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

local function checkfield(field)
	assert.type(field.name, "string", "field name")
	assert.type(field.type, "idl type", "field type")
end

local function checkfields(fields)
	for _, field in ipairs(fields) do checkfield(field) end
end

--------------------------------------------------------------------------------
-- Basic types -----------------------------------------------------------------

for name in pairs(BasicTypes) do
	local basictype = {_type = name}
	_M[name] = basictype
	BasicTypes[name] = basictype
end

--------------------------------------------------------------------------------
-- Scoped definitions management -----------------------------------------------

function setnameof(contained, name)
	contained.name = name
	local container = contained.defined_in
	if container then
		local start, default = match(container.repID, "^(IDL:.*):(%d+%.%d+)$")
		if not start then
			assert.illegal(container.repID, "parent scope repository ID")
		end
		contained.repID = format("%s/%s:%s", start, contained.name,
		                                     contained.version or default)
	else
		contained.repID = format("IDL:%s:%s", contained.name,
		                                      contained.version or "1.0")
	end
	if contained.definitions then
		for _, contained in ipairs(contained.definitions) do
			setnameof(contained, contained.name)
		end
	end
end

--------------------------------------------------------------------------------

ContainerKey = newproxy()

Contents = oo.class()

function Contents:__newindex(name, contained)
	if type(name) == "string" then
		contained.defined_in = self[ContainerKey]
		setnameof(contained, name)
		return self:_add(contained)
	end
	rawset(self, name, contained)
end

function Contents:_add(contained)
	UnorderedArraySet.add(self, contained)
	rawset(self, contained.name, contained)
	return contained
end

function Contents:_remove(contained)
	contained = UnorderedArraySet.remove(self, contained)
	if contained then
		self[contained.name] = nil
		return contained
	end
end

function Contents:_removeat(index)
	return self:remove(self[index])
end

Contents._removebyname = Contents._removeat

--------------------------------------------------------------------------------

function Container(self)
	if not oo.instanceof(self.definitions, Contents) then
		local contents = Contents{ [ContainerKey] = self }
		if self.definitions then
			for _, value in ipairs(self.definitions) do
				assert.type(value.name, "string", "IDL definition name")
				contents:__newindex(value.name, value)
			end
			for field, value in pairs(self.definitions) do
				if type(field) == "string" then
					contents:__newindex(field, value)
				end
			end
		end
		self.definitions = contents
	end
	return self
end

--------------------------------------------------------------------------------

function Contained(self)
	assert.type(self, "table", "IDL definition")
	if self.name  == nil then self.name = "" end
	if self.repID == nil then setnameof(self, self.name) end
	assert.type(self.name, "string", "IDL definition name")
	assert.type(self.repID, "string", "repository ID")
	return self
end

--------------------------------------------------------------------------------
-- User-defined type constructors ----------------------------------------------

-- Note: internal structure is optimized for un/marshalling.

string = { _type = "string", maxlength = 0 }

function Object(self)
	assert.type(self, "table", "Object type definition")
	if self.name  == nil then self.name = "" end
	if self.repID == nil then setnameof(self, self.name) end
	assert.type(self.name, "string", "Object type name")
	assert.type(self.repID, "string", "Object type repository ID")
	if self.repID == "IDL:omg.org/CORBA/Object:1.0"
		then self = object
		else self._type = "Object"
	end
	return self
end

function struct(self)
	self = Container(Contained(self))
	self._type = "struct"
	if self.fields == nil then self.fields = self end
	checkfields(self.fields)
	return self
end

function union(self)
	self = Container(Contained(self))
	self._type = "union"
	if self.options == nil then self.options = self end
	if self.default == nil then self.default = -1 end -- indicates no default in CDR
	
	assert.type(self.switch, "idl type", "union type discriminant")
	assert.type(self.options, "table", "union options definition")
	assert.type(self.default, "number", "union default option definition")
	
	self.selector = {} -- maps field names to labels (option selector)
	self.selection = {} -- maps labels (option selector) to options
	for index, option in ipairs(self.options) do
		checkfield(option)
		if option.label == nil then assert.illegal(nil, "option label value") end
		self.selector[option.name] = option.label
		if index ~= self.default + 1 then
			self.selection[option.label] = option
		end
	end
	
	function self.__index(union, field)
		if rawget(union, "_switch") == self.selector[field] then
			return rawget(union, "_value")
		end
	end
	function self.__newindex(union, field, value)
		local label = self.selector[field]
		if label then
			rawset(union, "_switch", label)
			rawset(union, "_value", value)
			rawset(union, "_field", field)
		end
	end
	
	return self
end

function enum(self)
	self = Contained(self)
	self._type = "enum"
	if self.enumvalues == nil then self.enumvalues = self end
	assert.type(self.enumvalues, "table", "enumeration values definition")
	
	self.labelvalue = {}
	for index, label in ipairs(self.enumvalues) do
		assert.type(label, "string", "enumeration value label")
		self.labelvalue[label] = index - 1
	end
	
	return self
end

function sequence(self)
	self._type = "sequence"
	if self.maxlength   == nil then self.maxlength = 0 end
	if self.elementtype == nil then self.elementtype = self[1] end
	assert.type(self.maxlength, "number", "sequence type maximum length ")
	assert.type(self.elementtype, "idl type", "sequence element type")
	return self
end

function array(self)
	self._type = "array"
	assert.type(self.length, "number", "array type length")
	if self.elementtype == nil then self.elementtype = self[1] end
	assert.type(self.elementtype, "idl type", "array element type")
	return self
end

ValueKind = {
	none = 0,
	custom = 1,
	abstract = 2,
	truncatable = 3,
}
ValueMemberAccess = {
	private = 0,
	public = 1,
}
for list in pairs{[ValueKind]=true, [ValueMemberAccess]=true} do
	local values = {}
	for _,value in pairs(list) do values[value] = true end
	for value in pairs(values) do list[value] = value end
end

function valuemember(self)
	self = Contained(self)
	self._type = "valuemember"
	checkfield(self)
	local access = ValueMemberAccess[self.access]
	if access == nil then
		assert.illegal(self.access, "value member access")
	end
	self.access = access
	return self
end

function valuetype(self)
	self = Container(Contained(self))
	self._type = "valuetype"
	local kind = self.kind or (self.truncatable and ValueKind.truncatable)
	                       or (self.abstract and ValueKind.abstract)
	                       or ValueKind.none
	kind = ValueKind[kind]
	if kind == nil then
		assert.illegal(self.kind, "value kind")
	end
	self.kind = kind
	local base = self.base_value
	if base == nil then
		self.base_value = null
	elseif base ~= null then
		assert.type(base, "idl valuetype", "base in value definition")
	end
	if self.members == nil then self.members = self end
	local members = self.members
	local definitions = self.definitions
	for index, member in ipairs(members) do
		member = valuemember(member)
		members[index] = member
		definitions:__newindex(member.name, member)
	end
	return self
end

function valuebox(self)
	self = Contained(self)
	self._type = "valuebox"
	if self.original_type == nil then self.original_type = self[1] end
	local type = self.original_type
	assert.type(type, "idl type", "type in typedef definition")
	local kind = type._type
	if kind == "valuetype" or kind == "valuebox" then
		assert.illegal(type, "type of value box")
	end
	return self
end

function typedef(self)
	self = Contained(self)
	self._type = "typedef"
	if self.original_type == nil then self.original_type = self[1] end
	assert.type(self.original_type, "idl type", "type in typedef definition")
	return self
end

function except(self)
	self = Container(Contained(self))
	self._type = "except"
	if self.members == nil then self.members = self end
	checkfields(self.members)
	return self
end

--------------------------------------------------------------------------------
-- IDL interface definitions ---------------------------------------------------

-- Note: construtor syntax is optimized for use with Interface Repository

function attribute(self)
	self = Contained(self)
	self._type = "attribute"
	if self.type  == nil then self.type = self[1] end
	assert.type(self.type, "idl type", "attribute type")

	local mode = self.mode
	if mode == "ATTR_READONLY" then
		self.readonly = true
	elseif mode ~= nil and mode ~= "ATTR_NORMAL" then
		assert.illegal(self.mode, "attribute mode")
	end
	
	return self
end

function operation(self)
	self = Contained(self)
	self._type = "operation"
	
	local mode = self.mode
	if mode == "OP_ONEWAY" then
		self.oneway = true
	elseif mode ~= nil and mode ~= "OP_NORMAL" then
		assert.illegal(self.mode, "operation mode")
	end

	self.inputs = {}
	self.outputs = {}
	if self.result and self.result ~= void then
		self.outputs[#self.outputs+1] = self.result
	end
	if self.parameters then
		for _, param in ipairs(self.parameters) do
			checkfield(param)
			if param.mode then
				assert.type(param.mode, "string", "operation parameter mode")
				if param.mode == "PARAM_IN" then
					self.inputs[#self.inputs+1] = param.type
				elseif param.mode == "PARAM_OUT" then
					self.outputs[#self.outputs+1] = param.type
				elseif param.mode == "PARAM_INOUT" then
					self.inputs[#self.inputs+1] = param.type
					self.outputs[#self.outputs+1] = param.type
				else
					assert.illegal(param.mode, "operation parameter mode")
				end
			else
				self.inputs[#self.inputs+1] = param.type
			end
		end
	end

	if self.exceptions then
		for _, except in ipairs(self.exceptions) do
			assert.type(except, "idl except", "raised exception")
			if self.exceptions[except.repID] ~= nil then
				assert.illegal(except.repID,
					"exception raise defintion, got duplicated repository ID")
			end
			self.exceptions[except.repID] = except
		end
	else
		self.exceptions = {}
	end

	return self
end

function module(self)
	self = Container(Contained(self))
	self._type = "module"
	return self
end

--------------------------------------------------------------------------------

local function ibases(queue, interface)
	interface = queue[interface]
	if interface then
		for _, base in ipairs(interface.base_interfaces) do
			queue:enqueue(base)
		end
		return interface
	end
end
function basesof(self)
	local queue = OrderedSet()
	queue:enqueue(self)
	return ibases, queue, OrderedSet.firstkey
end

function interface(self)
	self = Container(Contained(self))
	self._type = "interface"
	if self.base_interfaces == nil then self.base_interfaces = self end
	assert.type(self.base_interfaces, "table", "interface base list")
	for _, base in ipairs(self.base_interfaces) do
		if base._type ~= "abstract_interface" then
			assert.type(base, "idl interface", "interface base")
		end
	end
	self.hierarchy = basesof
	return self
end

function abstract_interface(self)
	self = interface(self)
	for _, base in ipairs(self.base_interfaces) do
		assert.type(base, "idl abstract_interface", "abstract interface base")
	end
	self._type = "abstract_interface"
	return self
end

function local_interface(self)
	self = interface(self)
	for _, base in ipairs(self.base_interfaces) do
		if base._type ~= "local_interface" then
			assert.type(base, "idl interface", "local interface base")
		end
	end
	self._type = "local_interface"
	return self
end

--------------------------------------------------------------------------------
-- IDL types used in the implementation of OiL ---------------------------------

object = interface{
	repID = "IDL:omg.org/CORBA/Object:1.0",
	name = "Object",
}
ValueBase = valuetype{
	repID = "IDL:omg.org/CORBA/ValueBase:1.0",
	name = "ValueBase",
	abstract = true,
}
OctetSeq = sequence{octet}
Version = struct{{ type = octet, name = "major" },
                 { type = octet, name = "minor" }}
