##############################################################################
#
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from osv import fields,osv
import ir
import netsvc

import time
import tools
import sql_db

class ir_model(osv.osv):
	_name = 'ir.model'
	_rec_name = 'model'
	_columns = {
		'name': fields.char('Name', size=64, translate=True),
		'model': fields.char('Model', size=64, required=True),
		'info': fields.text('Information'),
		'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
	}
	_defaults = {
		'name': lambda *a: 'No Name',
	}
ir_model()

class ir_model_fields(osv.osv):
	_name = 'ir.model.fields'
	_columns = {
		'name': fields.char('Name', size=64),
#TODO: this field (model) should go away !!!! it's the same as model_id.name
# note that it is at least used in osv/orm.py
		'model': fields.char('Model Name', size=64, required=True),
# on pourrait egalement changer ca en many2one, mais le prob c'est qu'alors faut
# faire une jointure a chaque fois qu'on recherche vu que le client ne connait que le nom
# de l'objet et pas son id
		'relation': fields.char('Model Relation', size=64),
		'model_id': fields.many2one('ir.model', 'Model id', required=True),
# in fact, this is the field label
		'field_description': fields.char('Field Description', size=256),
		'ttype': fields.char('Field Type', size=64),
		'relate': fields.boolean('Click and Relate'),

		'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
		'group_name': fields.char('Group Name', size=128),
		'view_load': fields.boolean('View Auto-Load'),
	}
	_defaults = {
		'relate': lambda *a: 1,
		'view_load': lambda *a: 0,
		'name': lambda *a: 'No Name',
		'field_description': lambda *a: 'No description available',
	}
	_order = "id"
ir_model_fields()

class ir_model_access(osv.osv):
	_name = 'ir.model.access'
	_columns = {
		'name': fields.char('Name', size=64, required=True),
		'model_id': fields.many2one('ir.model', 'Model', required=True),
		'group_id': fields.many2one('res.groups', 'Group'),
		'perm_read': fields.boolean('Read Access'),
		'perm_write': fields.boolean('Write Access'),
		'perm_create': fields.boolean('Create Access'),
	}
	def check(self, cr, uid, model_name, mode='read'):
		assert mode in ['read','write','create'], 'Invalid access mode for security'
		
		# fetch the list of rules for this "permission type" on this model
		cr.execute('select group_id, perm_'+mode+' from ir_model_access a left join ir_model m on (a.model_id=m.id) where m.model=%s', (model_name,))
		
		# if no rule is found, grant the access
		if not cr.rowcount:
			return True

		# compute the list of groups which have the right we are looking for
		ids = map(lambda x: str(x[0]), filter(lambda x: x[1], cr.fetchall()))
		if not ids:
			raise osv.except_osv('Access denied !', 'You can not %s this resource !' % mode)
		ids_str = ','.join(ids)
		
		# check if the user is part of one of those groups
		cr.execute('select count(*) from res_groups_users_rel where uid=%d and gid in ('+ids_str+')', (uid,))
		if cr.fetchone()[0]:
			return True
		raise osv.except_osv('Access denied !', 'You can not %s this resource !' % mode)
	check = tools.cache()(check)

	#
	# Methods to clean the cache on the Check Method.
	#
	def write(self, cr, uid, *args, **argv):
		res = super(ir_model_access, self).write(cr, uid, *args, **argv)
		self.check()
		return res
	def create(self, cr, uid, *args, **argv):
		res = super(ir_model_access, self).create(cr, uid, *args, **argv)
		self.check()
		return res
	def unlink(self, cr, uid, *args, **argv):
		res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
		self.check()
		return res
ir_model_access()

class ir_model_data(osv.osv):
	_name = 'ir.model.data'
	_columns = {
		'name': fields.char('XML Identifier', required=True, size=64),
		'model': fields.char('Model', required=True, size=64),
		'module': fields.char('Module', required=True, size=64),
		'res_id': fields.integer('Resource ID'),
		'noupdate': fields.boolean('Non Updatable'),
		'date_update': fields.datetime('Update Date'),
		'date_init': fields.datetime('Init Date')
	}
	_defaults = {
		'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
		'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
		'noupdate': lambda *a: False
	}

	def __init__(self):
		osv.osv.__init__(self)
		self.loads = {}
		self.init = True
		self.unlink_mark = {}

	def _get_id(self,cr, uid, module, xml_id):
		ids = self.search(cr, uid, [('module','=',module),('name','=', xml_id)])
		assert len(ids)==1, '%d reference(s) to %s. You should have only one !' % (len(ids),xml_id)
		return ids[0]

	def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
		if not xml_id:
			return False
		try:
			id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
			self.loads[(module,xml_id)] = (model,id)
		except:
			id = False
		return id

	def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init'):
		warning = True
		if xml_id and ('.' in xml_id):
			assert len(xml_id.split('.'))==2, '"%s" contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id' % (xml_id)
			warning = False
			module, xml_id = xml_id.split('.')
		if (not xml_id) and (not self.init):
			return False
		action_id = False
		res_id = False
		if xml_id:
			cr.execute('select id,res_id from ir_model_data where module=%s and name=%s', (module,xml_id))
			results = cr.fetchall()
			for action_id2,res_id2 in results:
				cr.execute('select id from '+self.pool.get(model)._table+' where id=%d', (res_id2,))
				result3 = cr.fetchone()
				if not result3:
					cr.execute('delete from ir_model_data where id=%d', (action_id2,))
				else:
					res_id,action_id = res_id2,action_id2

		if action_id:
			self.pool.get(model).write(cr, uid, [res_id], values)
			self.write(cr, uid, [action_id], {'date_update': time.strftime('%Y-%m-%d %H:%M:%S')})
		else:
			if mode=='init' or (mode=='update' and xml_id):
				res_id = self.pool.get(model).create(cr, uid, values)
				if xml_id:
					self.create(cr, uid, {'name':xml_id, 'model':model, 'module':module, 'res_id':res_id, 'noupdate':noupdate})
		if xml_id:
			if warning:
				assert (module,xml_id) not in self.loads, "id '%s' is already defined in module '%s' !" % (xml_id, module)
			if res_id:
				self.loads[(module,xml_id)] = (model, res_id)
		return res_id

	def _unlink(self, cr, uid, model, ids, direct=False):
		#self.pool.get(model).unlink(cr, uid, ids)
		for id in ids:
			self.unlink_mark[(model, id)]=False
			cr.execute('delete from ir_model_data where res_id=%d and model=\'%s\'', (id,model))
		return True

	def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None):
		obj = self.pool.get('ir.values')
		if type(models[0])==type([]) or type(models[0])==type(()):
			model,res_id = models[0]
		else:
			res_id=None
			model = models[0]
		if res_id:
			where = ' and res_id=%d' % (res_id,)
		else:
			where = ' and (res_id is null)'

		if key2:
			where += ' and key2=\'%s\'' % (key2,)
		else:
			where += ' and (key2 is null)'

		cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
		res = cr.fetchone()
		if not res:
			res = ir.ir_set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
		return True

	def _process_end(self, cr, uid, modules):
		if not modules:
			return True
		module_str = ["'%s'" % m for m in modules]
		cr.execute('select id,name,model,res_id,module from ir_model_data where module in ('+','.join(module_str)+') and not noupdate')
		wkf_todo = []
		for (id, name, model, res_id,module) in cr.fetchall():
			if (module,name) not in self.loads:
				self.unlink_mark[(model,res_id)] = id
				if model=='workflow.activity':
					cr.execute('select res_type,res_id from wkf_instance where id in (select inst_id from wkf_workitem where act_id=%d)', (res_id,))
					wkf_todo.extend(cr.fetchall())
					cr.execute("update wkf_transition set condition='True', role_id=NULL, signal=NULL,act_to=act_from,act_from=%d where act_to=%d", (res_id,res_id))
					cr.execute("delete from wkf_transition where act_to=%d", (res_id,))

		for model,id in wkf_todo:
			wf_service = netsvc.LocalService("workflow")
			wf_service.trg_write(uid, model, id, cr)

		cr.commit()
		for (model,id) in self.unlink_mark.keys():
			if self.pool.get(model):
				cr2 = sql_db.db.cursor()
				print 'Deleting %s@%s' % (id, model),
				try:
					self.pool.get(model).unlink(cr2, uid, [id])
					if self.unlink_mark[(model,id)]:
						self.unlink(cr2, uid, [self.unlink_mark[(model,id)]])
					cr2.commit()
					print '... ok'
				except:
					print '... ko'
					print '-'*60
					print 'WARNING: Could not delete id', id, 'of model', model
					print '\tThere should be some relation that points to this resource'
					print '\tYou should manually fix this and restart --update=module'
				cr2.close()
		return True
ir_model_data()

