##@mainpage 	MiniZinc Python Interface
# @author		Tai Tran
# @supervisor	Guido Tack


#@Things to add:
# Improve the interface so that there is no need to assign MZN_STDLIB_DIR when starting python
# args_ret_dict: some functions type checking are not written
# Hide some internal functions such as flatten or type_presentation

#@Suggestion:
# Remove the user-defined name when declaring variable
# Consider replacing it with i, where i is the number of arguments created
#	for example: calling [a,b,c] = m.Variable(1,100,3)

'''
@Usage:
definition:
	lb: lower bound,
	ub: upper bound,
	set:minizinc.Model.Set)


* Declaration *

minizinc.Model:
	accepts 1 optional argument:
		a string or list of strings indicating libraries to be included.
	returns:
		model to be used, for example:
			m = minizinc.Model
			i = m.Variable
			s = m.Set
			m.Constraint
			m.satisfy
			etc..
minizinc.Model.Set:
	accepts multiple arguments:
		item1, item2, ... itemn
		item?: a number - single element
			or a list/tuple of 2 elements - elements ranging from lb to ub
	returns:
		an object to be used
	functions:
		push - push more elements onto the set
		clear - clear all elements in the set
minizinc.Model.Variable:
	accepts up to 2 arguments:
		<None>			: a boolean variable
		ub				: a variable ranging from 0 to ub - 1
		lb, ub			: a variable ranging from lb to ub
		set				: a variable based on the set
minizinc.Model.Array:
minizinc.Model.Construct:




* Functions *
minizinc.Model.satisfy
minizinc.Model.next
'''

import sys
import minizinc_internal
import predicate
import annotation
#import inspect

if sys.version < '3':
	integer_types = (int, long, )
	python_types = (int, long, float, bool, str)
	int_t = long
	def longify(x):
		return long(x)
else:
	integer_types = (int, )
	python_types = (int, float, bool, str)
	int_t = int
	def longify(x):
		return x

# turns a complex structure of items into a list of items
def flatten(x):
	result = []
	for el in x:
		if isinstance(el, (list, tuple)):
			result.extend(flatten(el))
		else:
			result.append(el)
	return result

# nicely presents the type of a variable
def type_presentation(x):
	if x is None:
		return "Any_Type"
	if type(x) is type:
		name = x.__name__
		if name[:9] == 'minizinc.':
			name = name[9:]
		return name
	else:
		if type(x) is tuple:
			ret = '('
			for i, t in enumerate(x):
				if i != 0:
					ret += ', '
				ret += type_presentation(t)
			ret += ')'
		elif type(x) is list:
			ret = 'array' + str(len(x)) + 'd of ' + type_presentation(x[0])
		else:
			raise TypeError('Type Presentation: Unexpected type: ' + type(x))
		return ret



# All MiniZinc objects derived from here
class Expression(object):
	def __init__(self, model = None):
		self.model = model

	def __str__(self):
		return str(self.get_value())

	def is_decl(self):
		return isinstance(self, Declaration)

	def is_pre(self):
		return isinstance(self, Predicate)

	def evaluate(self, var):
		if isinstance(var, Expression):
			ret = var.get_value()
			if ret == None:
				raise ValueError("'" + var.name + "'Variable value is not set")
			return ret
		else:
			return var

	def eval_type(self, args):
		if isinstance(args, Expression):
			return args.type
		elif type(args) in integer_types:
			return int_t
		elif type(args) in (float, bool, str):
			return type(args)
		elif type(args) is list:
			t = None
			for i in args:
				if t is None:
					t = self.eval_type(i)
				else:
					type_i = self.eval_type(i)
					if t != type_i:
						raise TypeError("Type of arguments in an array must be the same: expected: " +
							type_presentation((t,)) + ", received: " + type_presentation((type_i,)) )
			return [t]
		elif type(args) is tuple:
			t = []
			for i in args:
				t.append(self.eval_type(i))
			return tuple(t)
		else:
			raise TypeError("Unexpected Type: " + type_presentation(type(args)) )

	def __and__(self, pred):
		return And( self, pred )

	def __rand__(self, pred):
		return And( self, pred )

	def __or__(self, pred):
		return Or( self, pred )

	def __xor__(self, pred):
		return Xor( self, pred )

	def __ror__(self, pred):
		return Or( self, pred )

	def __add__(self, pred):
		return Add( self, pred )

	def __radd__(self, pred):
		return Add( pred, self )

	def __sub__(self, pred):
		return Sub( self, pred )

	def __rsub__(self, pred):
		return Sub( pred, self )

	def __div__(self, pred):
		return Div( self, pred )

	def __rdiv__(self, pred):
		return Div( pred, self )

	def __floordiv__(self, pred):
		return FloorDiv( self, pred )

	def __rfloordiv__(self, pred):
		return FloorDiv( pred, self )

	def __mul__(self, pred):
		return Mul( self, pred )

	def __rmul__(self, pred):
		return Mul( self, pred )

	def __mod__(self, pred):
		return Mod( self, pred )

	def __rmod__(self, pred):
		return Mod( pred, self )

	def __pow__(self, pred):
		return Pow( self, pred )

	def __rpow__(self, pred):
		return Pow( pred, self )

	def __eq__(self, pred):
		return Eq( self, pred )

	def __ne__(self, pred):
		return Ne( self, pred )	

	def __lt__(self, pred):
		return Lt( self, pred )

	def __gt__(self, pred):
		return Gt( self, pred )

	def __le__(self, pred):
		return Le( self, pred )

	def __ge__(self, pred):
		return Ge( self, pred )

	def __neg__(self):
		return Neg( self )

	def __pos__(self):
		return Pos( self )

	def __invert__(self):
		return Invert( self )

	def __abs__(self):
		return Abs( self )


# Temporary container for Expression before evaluating to minizinc_internal object
# Calling Predicate.init automatically check:
#		if the arguments belong to the same model
class Predicate(Expression):
	def __init__(self, vars, args_and_return_type_tuple = None, name = None, model = None):
		self.vars = vars
		self.name = str(name)

		def eval_model(args):
			model = None
			for val in args:
				if isinstance(val, Expression):
					if hasattr(val, 'model'):
						if val.model is not None:
							if model is None:
								model = val.model
							elif model != val.model:
								raise TypeError("Arguments in the Predicate don't belong to the same model")
			return model

		self.vars_type = self.eval_type(vars)
		self.model = eval_model(vars)
		if args_and_return_type_tuple is not None:
			for t in args_and_return_type_tuple:
				def check_type(expected, actual):
					if expected is None:
						return True
					if len(expected) != len(actual):
						return False
					for i, val in enumerate(expected):
						if (val is None):
							pass
						elif type(val) is list and type(actual[i]) is list:
							if check_type(val, actual[i]) == False:
								return False
						elif type(val) != type(actual[i]):
							return False
					return True
				if check_type(t[0], self.vars_type):
				#self.vars_type == t[0]:
					self.type = t[1]
					break
			else:
				s = "MiniZinc: function '" + self.name + "': Argument type does not match, expected: " 
				for i, t in enumerate(args_and_return_type_tuple):
					if i != 0:
						s += ' or '
					s += type_presentation(t[0])
				s += ', received: ' + type_presentation(self.vars_type)
				raise TypeError(s)
		else:
			self.type = None
		Expression.__init__(self)


class BinOp(Predicate):
	def __init__(self, vars, code, args_and_return_type_tuple, name):
		Predicate.__init__(self,vars,args_and_return_type_tuple, name)
		self.BinOpCode = code

class UnOp(Predicate):
	def __init__(self, vars, code, args_and_return_type_tuple, name):
		Predicate.__init__(self,vars, args_and_return_type_tuple, name)
		self.UnOpCode = code

class Call(Predicate):
	def __init__(self, vars, code, args_and_return_type_tuple = None, name = None, model_list = None):
		Predicate.__init__(self,vars, args_and_return_type_tuple, name)
		self.CallCode = code
		self.model_list = model_list



args_ret_dict = {}

to_assign = [ ((int_t, int_t), int_t ),
			  ((float, float), float ),
			  ((int_t, float), float ),
			  ((float, int_t), float)]
args_ret_dict['add'] = to_assign
args_ret_dict['sub'] = to_assign
args_ret_dict['mul'] = to_assign
args_ret_dict['div'] = to_assign
args_ret_dict['floordiv'] = to_assign
args_ret_dict['pow'] = to_assign


args_ret_dict['mod'] =[ ((int_t, int_t), int_t )]

to_assign = [ ((int_t, int_t), bool ), 
			  ((float, float), bool ),
			  ((bool, bool), bool ),
			  ((str, str), bool ),
			  ((minizinc_internal.VarSet, minizinc_internal.VarSet), bool)]
args_ret_dict['lt'] = to_assign
args_ret_dict['le'] = to_assign
args_ret_dict['gt'] = to_assign
args_ret_dict['ge'] = to_assign
args_ret_dict['eq'] = to_assign
args_ret_dict['ne'] = to_assign



args_ret_dict['in'] = [ ((int_t, minizinc_internal.VarSet), bool) ]


to_assign = [ ((bool, bool), bool) ]
args_ret_dict['or']  = to_assign
args_ret_dict['and'] = to_assign
args_ret_dict['xor'] = to_assign

args_ret_dict['abort'] = [ ((), bool)]
to_assign = [ ((int_t,), int_t),
			  ((float,), float) ]
args_ret_dict['abs'] = to_assign
args_ret_dict['neg'] = to_assign
args_ret_dict['pos'] = to_assign
args_ret_dict['invert'] = to_assign

args_ret_dict['array1d'] = [ (None, [int_t]) ] #the function has checked its argument already
args_ret_dict['array2d'] = [ (None, [int_t, int_t]) ]
args_ret_dict['array3d'] = [ (None, [int_t, int_t, int_t]) ]
args_ret_dict['array4d'] = [ (None, [int_t, int_t, int_t, int_t])]
args_ret_dict['array5d'] = [ (None, [int_t, int_t, int_t, int_t, int_t])]
args_ret_dict['array6d'] = [ (None, [int_t, int_t, int_t, int_t, int_t, int_t])]

to_assign = [ ((float,), float) ]
args_ret_dict['sin']	= to_assign
args_ret_dict['cos']	= to_assign
args_ret_dict['tan']	= to_assign
args_ret_dict['asin']	= to_assign
args_ret_dict['acos']	= to_assign
args_ret_dict['atan']	= to_assign
args_ret_dict['sinh']	= to_assign
args_ret_dict['cosh']	= to_assign
args_ret_dict['tanh']	= to_assign
args_ret_dict['asinh']	= to_assign
args_ret_dict['acosh']	= to_assign
args_ret_dict['atanh']	= to_assign
args_ret_dict['sqrt']	= to_assign
args_ret_dict['ln']		= to_assign
args_ret_dict['log2']	= to_assign
args_ret_dict['log10']	= to_assign
args_ret_dict['exp']	= to_assign

args_ret_dict['log'] = [ ((float, float), float) ]


args_ret_dict['assert'] = [ ((bool, str), bool) ]
args_ret_dict['bool2int'] = [ ((bool,), int_t) ]
# XXX what to do with cardinality?
args_ret_dict['card'] = None

to_assign = [ ((float,), int_t) ]
args_ret_dict['ceil'] = to_assign
args_ret_dict['floor'] = to_assign
args_ret_dict['round'] = to_assign

to_assign = [ ((int_t,), float) ]
args_ret_dict['int2float'] = to_assign
# XXX consider remove
args_ret_dict['concat'] = None


to_assign = [ ((int_t, int_t), int_t),
			  ((float, float), float),
			  (([int_t],), int_t),
			  (([float],), float) ]
args_ret_dict['min'] = to_assign
args_ret_dict['max'] = to_assign

to_assign = [ (([int_t], ), int_t),
			  (([float], ), float) ]
args_ret_dict['product'] = to_assign
args_ret_dict['sum'] = to_assign


del to_assign



class Add(BinOp):
	def __init__(self, *vars):
		BinOp.__init__(self, vars, 0, args_ret_dict['add'], '+')

	def get_symbol(self):
		return '+'

	def get_value(self):
		lhs = self.evaluate(self.vars[0])
		rhs = self.evaluate(self.vars[1])
		if isinstance(rhs, str):
			lhs = str(lhs)
		elif isinstance(lhs, str):
			rhs = str(rhs)
		return lhs + rhs

class Sub(BinOp):
	def __init__(self, *vars):
		BinOp.__init__(self, vars, 1, args_ret_dict['sub'], '-')

	def get_symbol(self):
		return '-'

	def get_value(self):
		return self.evaluate(self.vars[0]) - self.evaluate(self.vars[1])

class Mul(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 2, args_ret_dict['mul'], '*')

	def get_symbol(self):
		return '*'

	def get_value(self):
		return self.evaluate(self.vars[0]) * self.evaluate(self.vars[1])

class Div(BinOp):

	def __init__(self, *vars) :
		BinOp.__init__(self, vars, 3, args_ret_dict['div'], '/')

	def get_symbol(self):
		return '/'

	def get_value(self):
		return self.evaluate(self.vars[0]) / self.evaluate(self.vars[1])

class FloorDiv(BinOp):

	def __init__(self, *vars) :
		BinOp.__init__(self, vars, 4, args_ret_dict['floordiv'], '//')

	def get_symbol(self):
		return '//'

	def get_value(self):
		return self.evaluate(self.vars[0]) // self.evaluate(self.vars[1])


class Mod(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 5, args_ret_dict['mod'], '%')

	def get_symbol(self):
		return '%'

	def get_value(self):
		return self.evaluate(self.vars[0]) % self.evaluate(self.vars[1])


class Lt(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 6, args_ret_dict['lt'], '<')

	def get_symbol(self):
		return '<'

	def get_value(self):
		return self.evaluate(self.vars[0]) < self.evaluate(self.vars[1])

class Le(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 7, args_ret_dict['le'], '<=')

	def get_symbol(self):
		return '<='

	def get_value(self):
		return self.evaluate(self.vars[0]) <= self.evaluate(self.vars[1])

class Gt(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 8, args_ret_dict['gt'], '>')

	def get_symbol(self):
		return '>'

	def get_value(self):
		return self.evaluate(self.vars[0]) > self.evaluate(self.vars[1])

class Ge(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 9, args_ret_dict['ge'], '>=')

	def get_symbol(self):
		return '>='

	def get_value(self):
		return self.evaluate(self.vars[0]) >= self.evaluate(self.vars[1])


class Eq(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 10, args_ret_dict['eq'], '==')

	def get_symbol(self):
		return '=='

	def get_value(self):
		return self.evaluate(self.vars[0]) == self.evaluate(self.vars[1])

class Ne(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 11, args_ret_dict['ne'], '!=')

	def get_symbol(self):
		return '!='

	def get_value(self):
		return self.evaluate(self.vars[0]) != self.evaluate(self.vars[1])

class In(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 12, args_ret_dict['in'], 'in')

	def get_symbol(self):
		return 'in'

	def get_value(self):
		return self.evaluate(self.vars[0]) in self.evaluate(self.vars[1])

	def __nonzero__(self):
		return self


class Or(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 23, args_ret_dict['or'], 'or')

	def get_symbol(self):
		return 'or'

	def get_value(self):
		return self.evaluate(self.vars[0]) or self.evaluate(self.vars[1])

class And(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 24, args_ret_dict['and'], 'and')

	def get_symbol(self):
		return '&'

	def get_value(self):
		return self.evaluate(self.vars[0]) & self.evaluate(self.vars[1])

class Xor(BinOp):

	def __init__(self, *vars):
		BinOp.__init__(self, vars, 25, args_ret_dict['xor'], 'xor')

	def get_symbol(self):
		return 'xor'

	def get_value(self):
		return bool(self.evaluate(self.vars[0])) != bool(self.evaluate(self.vars[1]))

class Pow(Call):
	def __init__(self, *vars):
		Call.__init__(self, vars, "pow", args_ret_dict['pow'], '^')

	def get_symbol(self):
		return '**'

	def get_value(self):
		return self.evaluate(self.vars[0]) ** self.evaluate(self.vars[1])

class Abort(Call):
	def __init__(self, *vars):
		Call.__init__(self, vars, "abort", args_ret_dict['abort'], 'abort')

class Abs(Call):
	def __init__(self, *vars):
		Call.__init__(self, vars, "abs", args_ret_dict['abs'], 'abs')


class Array1d(Call):
	def __init__(self, dim1, array):
		Call.__init__(self,(dim1, array),"array1d", args_ret_dict['array1d'], 'array1d')

class Array2d(Call):
	def __init__(self, dim1, dim2, array):
		Call.__init__(self, (dim1, dim2, array), "array2d", args_ret_dict['array2d'], 'array2d')

class Array3d(Call):
	def __init__(self, dim1, dim2, dim3, array):
		Call.__init__(self,[dim1, dim2, dim3, array],"array3d", args_ret_dict['array3d'], 'array3d')

class Array4d(Call):
	def __init__(self, dim1, dim2, dim3, dim4, array):
		Call.__init__(self,[dim1, dim2, dim3, dim4, array],"array4d", args_ret_dict['array4d'], 'array4d')

class Array5d(Call):
	def __init__(self, dim1, dim2, dim3, dim4, dim5, array):
		Call.__init__(self,[dim1, dim2, dim3, dim4, dim5, array],"array5d", args_ret_dict['array5d'], 'array5d')

class Array6d(Call):
	def __init__(self, dim1, dim2, dim3, dim4, dim5, dim6, array):
		Call.__init__(self,[dim1, dim2, dim3, dim4, dim5, dim6, array],"array6d", args_ret_dict['array6d'], 'array6d')

class Acos(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "acos", args_ret_dict['acos'], 'acos')

class Acosh(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "acosh", args_ret_dict['acosh'], 'acosh')

class Asin(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "asin", args_ret_dict['asin'], 'asin')

class Asinh(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "asinh", args_ret_dict['asinh'], 'asinh')

class Atan(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "atan", args_ret_dict['atan'], 'atan')

class Atanh(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "atanh", args_ret_dict['atanh'], 'atanh')

class Assert(Call):
	def __init__(self, constraint, message):
		Call.__init__(self, (constraint, message), "assert", args_ret_dict['assert'], 'assert')

class Bool2Int(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "bool2int", args_ret_dict['bool2int'], 'bool2int')

class Card(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "card", args_ret_dict['card'], 'card')

class Ceil(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "ceil", args_ret_dict['ceil'], 'ceil')

class Concat(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "concat",)

class Cos(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "cos", args_ret_dict['cos'], 'cos')

class Cosh(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "cosh", args_ret_dict['cosh'], 'cosh')

class Dom(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"dom")

class Dom_Array(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"dom_array")

class Dom_Size(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"dom_size")

class Fix(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"fix")

class Floor(Call):
	def __init__(self, var):
		Call.__init__(self, (var,),"floor", args_ret_dict['floor'], 'floor')

class Exp(Call):
	def __init__(self, var):
		Call.__init__(self, (var,),"exp", args_ret_dict['exp'], 'exp')

class Int2Float(Call):
	def __init__(self, var):
		Call.__init__(self, (var,),"int2float", args_ret_dict['int2float'], 'int2float')

class Is_Fixed(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"is_fixed")

class Join(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"join")

class Lb(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"lb")

class Lb_array(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"lb_array")

class Length(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"length")

class Ln(Call):
	def __init__(self, var):
		Call.__init__(self, (var,),"ln", args_ret_dict['ln'], 'ln')

class Log(Call):
	def __init__(self, var1, var2):
		Call.__init__(self, (var1, var2),"log", args_ret_dict['log'], 'log')

class Log2(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "log2", args_ret_dict['log2'], 'log2')

class Log10(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "log10", args_ret_dict['log10'], 'log10')

class Min(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "min", args_ret_dict(['min']), 'min')

class Max(Call):
	def __init__(self, var):
		Call.__init__(self, (var,), "max", args_ret_dict(['max']), 'max')

class Product(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"product", args_ret_dict['product'], 'product')

class Round(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"round", args_ret_dict['round'], 'round')

class Set2array(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"set2array")

class Sin(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"sin", args_ret_dict['sin'], 'sin')

class Sinh(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"sinh", args_ret_dict['sinh'], 'sinh')

class Sqrt(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"sqrt", args_ret_dict['sqrt'], 'sqrt')

class Sum(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"sum", args_ret_dict['sum'], 'sum')

class Tan(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"tan", args_ret_dict['tan'], 'tan')

class Tanh(Call):
	def __init__(self, var):
		Call.__init__(self, (var, ),"tanh", args_ret_dict['tanh'], 'tanh')

class Trace(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"trace")

class Ub(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"ub")

class Ub_Array(Call):
	def __init__(self, var):
		Call.__init__(self,[var],"ub_array")


class Neg(UnOp):
	def __init__(self, var):
		UnOp.__init__(self, (var, ), 2, args_ret_dict['neg'], 'neg')

class Pos(UnOp):
	def __init__(self, var):
		UnOp.__init__(self, (var, ), 1, args_ret_dict['pos'], 'pos')

class Invert(Predicate):
	def __init__(self, vars):
		UnOp.__init__(self, (var, ), 0, args_ret_dict['invert'], 'invert')

class Id(Expression):
	def __init__(self, name, model_list = None):
		self.name = name
		self.model_list = model_list
		self.type = minizinc_internal.Annotation


# Temporary container for Variable Declaration
class Declaration(Expression):
	def __init__(self, model):
		if not isinstance(model, Model):
			raise TypeError('Warning: First argument must be a Model Object')
		Expression.__init__(self, model)
		self.name = '"' + str(id(self)) + '"'
		self.solve_counter = -1
		self.next_counter = -1
		self.value = None

	def get_value(self):
		if self.solve_counter == self.model.solve_counter and self.next_counter == self.model.next_counter:
			return self.value
		self.solve_counter = self.model.solve_counter
		self.next_counter = self.model.next_counter
		self.value = self.model.mznsolver.get_value(self.name)
		return self.value

	def __str__(self):
		return str(self.get_value())

	def __repr__(self):
		return 'A Minizinc ' + self.__class__.__name__ + ' with the value of ' + self.__str__()


class Construct(Declaration):
	def __init__(self, model, arg1):
		Declaration.__init__(self, model)
		self.type = self.eval_type(arg1)
		self.has_minizinc_objects = False

		def unwrap(arg):
			if isinstance(arg, Expression):
				self.has_minizinc_objects = True
				return arg.obj
			elif type(arg) in (list, tuple):
				ret = []
				for val in arg:
					ret.append(unwrap(val))
				return ret
			elif type(arg) in python_types:
				return arg
			else:
				raise TypeError('Unexpected type: argument should be a single/list/tuple of MiniZinc objects or Python basic types')

		try:
			self.obj = model.mznmodel.Declaration(self.name, unwrap(arg1))
		except:
			print sys.exc_info()[0]
			raise

		if self.has_minizinc_objects:
			self.value = arg1
		else:
			self.wrapped_value = arg1
		self.class_name = 'Construct'

	'''
		imagine we have:
			a = Variable(1,100)				undefined
			b = Variable(1,100)				undefined

			c = Construct([a,b])			[ undefined, undefined ]
		when the model is solved, c's value should be changed
	'''

	def get_value(self):
		if self.has_minizinc_objects:
			def get_value_helper(arg):
				if isinstance(arg, Expression):
					return arg.get_value()
				elif type(arg) is list:
					ret = []
					for val in arg:
						ret.append(get_value_helper(val))
					return ret
				elif type(arg) in python_types:
					return arg
				else:
					raise TypeError('Internal error: Unexpected type')
			if not (self.solve_counter == self.model.solve_counter and self.next_counter == self.obj.next_counter):
				self.value = get_value_helper(self.wrapped_value)
		return self.value




class Variable(Declaration):
	def __init__(self, model, arg1=None, arg2=None):
		Declaration.__init__(self, model)
		lb, ub = False, True
		if arg1 is not None:
			typearg1 = type(arg1)
			if arg2 is None:
				if typearg1 is Set:
					lb = arg1
					ub = None
				elif typearg1 in (list,tuple):
					if len(arg1) != 2:
						raise ValueError('Requires a list or tuple of exactly 2 numbers')
					lb,ub = sorted(arg1)[0,1]
				else:
					ub = arg1 - 1
					lb = typearg1(lb)
			else:
				lb,ub = arg1, arg2

		typelb, typeub = type(lb), type(ub)
		if not typelb is Set:
			if typelb not in (bool, float, ) and typelb not in integer_types and not isinstance(typelb, Declaration):
				raise TypeError('Lower bound must be a boolean, an int, a float or a set')
			if typeub not in (bool, float, ) and typeub not in integer_types and not isinstance(typelb, Declaration):
				raise TypeError('Upper bound must be a boolean, an int or a float')
			if typelb != typeub:
				raise TypeError('Upper bound an dlower bound is of different type')
			if lb > ub:
				raise ValueError('Lower bound cannot be greater than upper bound')

		self.dim_list = []

		if typelb is bool:
			self.obj = model.mznmodel.Declaration(self.name, 10, [])
			self.type = bool
		elif typelb in integer_types:
			self.obj = model.mznmodel.Declaration(self.name, 9, [], lb, ub)
			self.type = int_t
		elif typelb is float:
			self.obj = model.mznmodel.Declaration(self.name, 11, [], lb, ub)
			self.type = float
		elif typelb is Set:
			self.obj = model.mznmodel.Declaration(self.name, 9, [], lb.obj)
			self.type = int_t
		else:
			raise TypeError('Internal: Unexpected type')

		self.class_name = 'Variable'

class VariableConstruct(Variable, Construct):
	def __init__(self, model, arg1, arg2 = None):
		Construct.__init__(self, model, arg1, arg2)

class Array(Variable):
	def __init__(self, model, argopt1, argopt2, *args):
		Declaration.__init__(self, model)
		dim_list = []
		lb = None
		ub = None

		def add_to_dim_list(i):
			if type(i) is int:
				if i > 0:
					dim_list.append([0,i-1])
				else:
					raise TypeError('Single value must be a positive integer')
			elif type(i) is list:
				if type(i[0]) in integer_types and type(i[-1]) in integer_types:
					dim_list.append([ i[0],i[-1]] )
				else:
					raise TypeError('Range boundaries must be integers')
			elif isinstance(i, Set):
				if i.continuous():
					dim_list.append([i.min(), i.max()])
				else:
					raise TypeError('Array ranges must be continuous')
			else:
				raise TypeError('Unknown type')

		if type(argopt1) is Set:
			lb = argopt1
			ub = None
			add_to_dim_list(argopt2)
		elif type(argopt1) is bool and type(argopt2) is bool:
			lb = argopt1
			ub = argopt2
		elif type(argopt2) not in integer_types:
			if type(argopt1) not in integer_types:
				raise TypeError('Range values must be integers')
			lb = 0
			ub = argopt1 - 1
			add_to_dim_list(argopt2)
		else:
			if type(argopt1) not in integer_types or type(argopt2) not in integer_types:
				raise TypeError('Lower bound and upper bound must be integers')
			lb = argopt1
			ub = argopt2

		for i in args:
			add_to_dim_list(i)

		self.lb = lb
		self.ub = ub
		if dim_list == []:
			raise AttributeError('Initialising an Array without dimension list')
		self.dim_list = dim_list
		tlb = type(argopt1)
		if tlb is bool:
			self.type = [bool] * len(dim_list)
			self.obj = model.mznmodel.Declaration(self.name,10,dim_list,lb,ub)
		elif tlb in integer_types:
			self.type = [int_t] * len(dim_list)
			self.obj = model.mznmodel.Declaration(self.name,9,dim_list, lb, ub)
		elif tlb is float:  #isinstance(lb, float):
			self.type = [float] * len(dim_list)
			self.obj = model.mznmodel.Declaration(self.name,11,dim_list,lb,ub)
		elif tlb is Set:
			self.type = [int_t] * len(dim_list)
			self.obj = model.mznmodel.Declaration(self.name,9,dim_list,lb.obj)
		else:
			raise TypeError('Unexpected type')

		self.class_name = 'Array'

	def __getitem__(self, *args):
		return ArrayAccess(self.model, self, args[0])


# XXX: to be rewritten later
class ArrayAccess(Array):
	def __init__(self, model, array, idx):
		Declaration.__init__(self, model)
		if type(idx) is not tuple:
			idx = [idx]
		else:
			idx = flatten(idx)
		if len(idx) != len(array.dim_list):
			raise IndexError('Requires exactly ' + str(len(self.dim_list)) + ' index values')
		for i, value in enumerate(idx):
			if not isinstance(value, Expression):
				if value < array.dim_list[i][0] or value > array.dim_list[i][1]:
					raise IndexError('Index at pos ' + str(i) + ' is out of range')

		self.array = array
		self.idx = idx
		self.type = array.type[0]
		self.value = None
		self.class_name = 'Array Item'

	def get_value(self):
		if hasattr(self, 'solve_counter'):
			if self.solve_counter == self.model.solve_counter and self.next_counter == self.model.next_counter:
				return self.value
			self.solve_counter = self.model.solve_counter
			self.next_counter = self.model.next_counter
			arrayvalue = self.array.get_value()
			if arrayvalue is None:
				return None
			else:
				for i,value in enumerate(self.idx):
					arrayvalue = arrayvalue[value - self.array.dim_list[i][0]]
				self.value = arrayvalue
				return self.value
		return self.value

class ArrayConstruct(Array, Construct):
	def __init__(self, model, arg1, arg2 = None):
		Construct.__init__(self, model, arg1, arg2)


class Set(Declaration):
	# Set is model independent and can be reused in multiple models
	# Thus, Set.model = None
	def __init__(self, *args):
		self.model = None
		self.name = '"' + str(id(self)) + '"'
		'''
		lb, ub = None, None
		set_list = None

		if argopt2 is not None:
			lb,ub = argopt1, argopt2
		else:
			if type(argopt1) is list:
				set_list = argopt1
			else:
				ub = argopt1 - 1
				lb = 0

		if set_list is None:
			if not (type(lb) in integer_types and type(ub) in integer_types):
				raise TypeError('Lower bound and upper bound must be integers')
			set_list = [[lb, ub]]
		'''
		self.obj = minizinc_internal.Set(*args)
		self.type = minizinc_internal.Set
		self.class_name = 'Set'

	def push(self, *args):
		self.obj.push(*args)

	def clear(self):
		self.obj.clear()

	def continuous(self):
		return self.obj.continuous()

	def min(self):
		return self.obj.min()

	def max(self):
		return self.obj.max()

	def get_value(self):
		return self.obj

	def __iter__(self):
		return self.obj.__iter__()

	def __contains__(self, val):
		return self.obj.contains(val)

class VarSet(Variable):
	def __init__(self, model, argopt1, argopt2 = None):
		self.model = model
		self.name = '"' + str(id(self)) + '"'

		lb, ub = None, None
		set_list = None
		if argopt2 is not None:
			lb,ub = argopt1, argopt2
		else:
			if type(argopt1) is list:
				set_list = argopt1
			elif type(argopt1) is Set:
				lb = argopt1.min()
				ub = argopt1.max()
			else:
				ub = argopt1 - 1
				lb = 0

		if set_list is None:
			if not (type(lb) in integer_types and type(ub) in integer_types):
				raise TypeError('Lower bound and upper bound must be integers')

		model.mznmodel.Declaration(self.name, 12, [], lb, ub)
		self.type = minizinc_internal.VarSet
		self.class_name = 'Var Set'

	'''	Python automatiCally evaluate a __contains__ return object to boolean
		thus, we have to use seperate In class instead of 
			for i in VarSet
	def __contains__(self, argopt):
		return In([argopt, self])
	'''

variable_model_dict = {}
function_model_dict = {}
name_model_dict = {}
def handlerFunctionClosure(name, args_list, model_list):
	def handlerFunction(*args):
		return Call(args, name, args_list, name, model_list)
	return handlerFunction

def handlerFunction(name, model_list):
	return Id(name, model_list)

def init(args = None, model = None):
	def assign_usable_names_to_model(name, model):
		if name in name_model_dict:
			if model is None:
				name_model_dict[name] = None
			else:
				name_model_dict[name].append(model)
			return False
		else:
			if model is None:
				name_model_dict[name] = None
			else:
				name_model_dict[name] = [model]
			return True

	names = minizinc_internal.retrieveNames(args)
	for name, args_and_return_type_tuple in names["boolfuncs"].items():
		if assign_usable_names_to_model(name, model):
			setattr(predicate, name, handlerFunctionClosure(name, args_and_return_type_tuple, name_model_dict[name]))

	for name, args_and_return_type_tuple in names["annfuncs"].items():
		if assign_usable_names_to_model(name, model):
			setattr(predicate, name, handlerFunctionClosure(name, args_and_return_type_tuple, name_model_dict[name]))

	for name in names["annvars"]:
		if assign_usable_names_to_model(name, model):
			setattr(annotation, name, handlerFunction(name, name_model_dict[name]))

init()

class Model(object):
	def __init__(self, args = None):
		self.loaded = False
		self.mznsolver = None
		self.mznmodel = minizinc_internal.Model(args)
		if args:
			init(args, self)
		self.solve_counter = -1
		self.next_counter = -1
		

	'''
	# not used anymore
	def get_name(self,var):
		itemList = [var_name for var_name, var_val in self.frame if var_val is var]
		if len(itemList) == 1:
			return itemList[0]
		elif len(itemList) == 0:
			raise LookupError('Internal Error: variable name not found')
		else:
			raise LookupError('The object pointed to was assigned to different names')
	'''
	
	def add_recursive(self, expr):
		if isinstance(expr, (tuple, list)):
		    for i in expr:
		        self.add_recursive(i)
		else:
			if issubclass(type(expr), Expression):
				if expr.is_pre():
					self.mznmodel.Constraint(self.evaluate(expr))
				else:
					raise(TypeError, "Unexpected Expression")
			else:
				self.mznmodel.Constraint(expr)

	def Constraint(self, *expr):
		minizinc_internal.lock()
		if len(expr)>0:
			self.loaded = True
			#self.frame = inspect.currentframe().f_back.f_locals.items()
			self.add_recursive(expr)
			#del self.frame
		minizinc_internal.unlock()

	def evaluate(self, expr):
		if not isinstance(expr, Expression):
			if isinstance(expr, (list, tuple)):
				for i,item in enumerate(expr):
					expr[i] = self.evaluate(item)
			return expr
		if isinstance(expr, Id):
			if expr.model_list is not None:
				if not self in expr.model_list:
					raise TypeError("The annotation '" + expr.name + "' does not belong to the model")
			return minizinc_internal.Id(expr.name)
		if isinstance(expr, Call):
			variables = []
			model = None
			for i in expr.vars:
				variables.append(self.evaluate(i))
			if expr.model_list is not None:
				if not self in expr.model_list:
					raise TypeError("The function '" + expr.name + "' does not belong to the model")
			return minizinc_internal.Call(expr.CallCode, variables, expr.type)
		elif isinstance(expr, ArrayAccess):
			for i,value in enumerate(expr.idx):
				if isinstance(value, Expression):
					expr.idx[i] = self.evaluate(value)
			return minizinc_internal.at(expr.array.obj, expr.idx)
		elif isinstance(expr, Declaration):
			if expr.model != self:
				raise TypeError("The Declaration not belongs to this model")
			return expr.obj
		elif isinstance(expr, BinOp):
			if expr.model is not None and expr.model != self:
				raise TypeError("The Declaration not belongs to this model")
			lhs = self.evaluate(expr.vars[0])
			rhs = self.evaluate(expr.vars[1])
			return minizinc_internal.BinOp(lhs, expr.BinOpCode, rhs)
		elif isinstance(expr, UnOp):
			if expr.model is not None and expr.model != self:
				raise TypeError("The Declaration not belongs to this model")
			rhs = self.evaluate(expr.vars[0])
			return minizinc_internal.UnOp(expr.UnOpCode, rhs)
		else:
			raise TypeError('Variable Type unspecified')



	def Variable(self, argopt1=None, argopt2=None):
		return Variable(self, argopt1, argopt2)

	def Array(self, argopt1, argopt2, *args):
		list_ = []
		for i in args:
			list_.append(i)
		return Array(self, argopt1, argopt2, *list_)

	def Set(self, *args):
		return Set(*args)

	def Range(self, arg1, arg2 = None):
		if (arg2 is None):
			return Set((0, arg1 - 1))
		else:
			return Set((arg1, arg2))

	def VarSet(self, argopt1, argopt2 = None):
		return VarSet(self, argopt1, argopt2)

	def Construct(self, argopt1):
		if isinstance(argopt1, list):
			return ArrayConstruct(self, argopt1)
		else:
			return VariableConstruct(self, argopt1)

	def __solve(self, code, expr, ann, data, solver, time):
		minizinc_internal.lock()

		if ann is not None:
			if not hasattr(ann, 'type') or ann.type != minizinc_internal.Annotation:
				raise TypeError('Unexpected type of annotation')
		eval_ann = self.evaluate(ann)
		eval_expr = self.evaluate(expr)

		savedModel = self.mznmodel.copy()

		# The model with declared variable cannot be deleted.
		self.mznmodel, savedModel = savedModel, self.mznmodel
		self.mznmodel.SolveItem(code, eval_ann, eval_expr)
		if data is not None:
			self.add_recursive(data)
		minizinc_internal.unlock()

		self.mznsolver = self.mznmodel.solve(solver = solver, time = time)
		self.mznmodel = savedModel
		self.solve_counter = self.solve_counter + 1
		self.next_counter = -1


	def satisfy(self, ann = None, data = None, solver = 'gecode', time = 0):
		self.__solve(0, None, ann, data, solver, time)
	def maximize(self, expr, ann = None, data = None, solver = 'gecode', time = 0):
		self.__solve(2, expr, ann, data, solver, time)
	def minimize(self, expr, ann = None, data = None, solver = 'gecode', time = 0):
		self.__solve(1, expr, ann, data, solver, time)
	def reset(self):
		self.__init__()

	def next(self):
		if self.mznsolver is None:
			raise ValueError('Model is not solved yet')
		self.status = self.mznsolver.next()
		self.next_counter = self.next_counter + 1
		return (self.status is None)

	def is_loaded(self):
		return self.loaded

	def is_solved(self):
		return self.mznsolver != None

	def set_time_limit(self, time):
		self.mznmodel.set_time_limit(time)

	def set_solver(self, solver):
		self.mznmodel.set_solver(solver)

	def _debugprint(self):
		self.mznmodel.debugprint()

