require 'thread'

class Sfp::Runtime
	attr_reader :modules

	def initialize(model)
		@mutex_procedure = Mutex.new
		@root = model
		@modules = nil
	end

	def execute_action(action)
		def normalise_parameters(params)
			p = {}
			params.each { |k,v| p[k[2,k.length-2]] = v }
			p
		end

		self.get_state if not defined?(@modules) or @modules.nil?

		module_path, method_name = action['name'].pop_ref
		mod = @modules.at?(module_path)[:_self]
		raise Exception, "Module #{module_path} cannot be found!" if mod.nil?
		raise Exception, "Cannot execute #{action['name']}!" if not mod.respond_to?(method_name)

		params = normalise_parameters(action['parameters'])
		if mod.synchronized.rindex(method_name)
			@mutex_procedure.synchronize { mod.send method_name.to_sym, params }
		else
			mod.send method_name.to_sym, params
		end

		# TODO - check post-execution state for verification
	end

	def get_state(as_sfp=false)
		def cleanup(model)
			model.select { |k,v| k[0,1] != '_' and !(v.is_a?(Hash) and v['_context'] != 'object') }
		end

		def add_hidden_attributes(model, state)
			model.each { |k,v|
				state[k] = v if (k[0,1] == '_' and k != '_parent') or
					(v.is_a?(Hash) and v['_context'] == 'procedure')
			}
		end

		# Load the implementation of an object, and return its current state
		# @param model a Hash
		# @return a Hash which is the state of the object
		#
		def instantiate_module(model, root, as_sfp=false)
			# extract class name
			class_name = model['_isa'].sub(/^\$\./, '')

			# throw an exception if schema's implementation is not exist!
			raise Exception, "Implementation of schema #{class_name} is not available!" if
				not Sfp::Module.const_defined?(class_name)

			# create an instance of the schema
			mod = Sfp::Module::const_get(class_name).new
			default = cleanup(root.at?(model['_isa']))
			ruby_model = cleanup(model)
			mod.init(ruby_model, default)

			# update synchronized list of procedures
			model.each { |k,v|
				next if k[0,1] == '_' or not (v.is_a?(Hash) and v['_context'] == 'procedure')
				mod.synchronized << k if v['_synchronized']
			}

			# return the object instant
			mod
		end

		# Return the state of an object
		#
		def get_object_state(model, root, as_sfp=false, path='$')
			modules = {}
			state = {}
			if model['_context'] == 'object' and model['_isa'].to_s.isref
				if model['_isa'] != '$.Object'
					# if this model is an instance of a subclass of Object, then
					# get the current state of this object
					#modules[:_self] = nil
					mod = (!defined?(@modules) or @modules.nil? ? nil : @modules.at?(path))
					if mod.is_a?(Hash)
						modules[:_self] = mod[:_self]
					else
						# the module has not been instantiated yet!
						modules[:_self] = instantiate_module(model, root, as_sfp)
					end
					# update and get the state
					modules[:_self].update_state
					state = modules[:_self].state
					if !mod.nil? and mod.has_key?(:_vars)
						state.keep_if { |k,v| mod[:_vars].index(k) }
						modules[:_vars] = mod[:_vars]
					else
						modules[:_vars] = state.keys
					end
					# set hidden attributes
					add_hidden_attributes(model, state) if as_sfp
				end
			end

			# get the state for each attributes which are not covered by this
			# object's module
			(model.keys - state.keys).each do |key|
				next if key[0,1] == '_'
				if model[key].is_a?(Hash)
					modules[key], state[key] = get_object_state(model[key], root, as_sfp, path.push(key)) if
						model[key]['_context'] == 'object'
					modules[key]['_parent'] = modules if modules[key].is_a?(Hash)
				else
					state[key] = Sfp::Undefined.new
				end
			end

			[modules, state]
		end

		root = Sfp::Helper.deep_clone(@root)
		root.accept(ParentEliminator)
		@modules, state = get_object_state(root, root, as_sfp)
		@modules.accept(ParentGenerator)

		state
	end

	protected
	ParentEliminator = Sfp::Visitor::ParentEliminator.new

	ParentGenerator = Object.new
	def ParentGenerator.visit(name, value, parent)
		value['_parent'] = parent if value.is_a?(Hash)
		true
	end

end