#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import sys
import time
import types

from winswitch.util.simple_logger import Logger
from winswitch.util.common import get_bool
logger = Logger("object.common")

DEBUG_FIELD_UPDATES = False												#shows which fields are updated by update_fields()
DEBUG_CALLBACKS = "--debug-status-callbacks" in sys.argv				#shows which callbacks are added, which ones fire, and why

EXPERIMENTAL = "--enable-experimental" in sys.argv
ALLOW_PRINTER_SHARING	= EXPERIMENTAL or "--enable-printing" in sys.argv
ALLOW_FILE_SHARING		= EXPERIMENTAL or "--enable-samba" in sys.argv


class ModifiedCallbackObject:
	"""
	Utility superclass for objects that record their modification time.
	We can register callback to fire whenever the object is modified by calling touch()
	The callback must return True or it will be removed from the list. (ie: can be used to fire just once)
	"""

	def __init__(self):
		Logger(self, log_colour=Logger.CYAN)
		self.modified_callbacks = []
		self.last_updated = time.time()
		self.timed_out = False
		self.debug_callbacks = DEBUG_CALLBACKS

	def __cmp__(self, other):
		if type(other)!=type(self):
			return -1
		c = self.__hash__()-other.__hash__()
		if c!=0:
			return c
		return	int((self.last_updated - other.last_updated) * 1000 * 1000)

	def timedout(self, time):
		self.timed_out = not self.was_updated_since(time)
		return	self.timed_out

	def was_updated_since(self, time):
		return	time < self.last_updated

	def touch(self):
		#TODO: locking! (I miss "synchronized") - simple lock didn't work (callbacks can fire touch() again...)
		self.last_updated = time.time()
		self.fire_touch_callbacks()

	def fire_touch_callbacks(self):
		new_callbacks = []
		for callback in self.modified_callbacks:
			if self.debug_callbacks:
				self.sdebug("calling %s" % callback)
			try:
				if callback():
					new_callbacks.append(callback)
			except Exception, e:
				self.serr("error on callback %s" % callback, e)
		if self.debug_callbacks:
			self.sdebug("callbacks=%s, new_callbacks=%s" % (str(self.modified_callbacks), str(new_callbacks)))
		self.modified_callbacks = new_callbacks

	def add_modified_callback(self, callback):
		self.modified_callbacks.append(callback)

	def update_fields(self, fields, from_obj, touch=True, touch_if_notmodified=True):
		updates = {}
		for field in fields:
			try:
				cur = getattr(self, field)
			except AttributeError, e:
				self.serr("BUG: cannot find field %s in %s" % (field, type(self)), e, fields, from_obj, touch, touch_if_notmodified)
				continue
			_new = getattr(from_obj, field)
			if cur==_new:
				continue
			if DEBUG_FIELD_UPDATES:
				cl = len(str(cur))
				nl = len(str(_new))
				if nl>=80 or cl>=80:
					self.sdebug("updating %s.%s, copied %d bytes (previously %d bytes)" % (self, field, nl, cl), fields, from_obj)
				else:
					self.sdebug("updating %s.%s from '%s' to '%s'" % (self, field, cur, _new), fields, from_obj)
			updates[field] = _new
			try:
				setattr(self, field, _new)
			except Exception, e:
				self.exc(None, e, fields, from_obj)
		if DEBUG_FIELD_UPDATES:
			self.sdebug("updated: %s on %s" % (str(updates), self), fields, from_obj)
		if (touch_if_notmodified or len(updates.keys())>0) and touch:
			self.touch()
		return	len(updates.keys())>0



def add_argv_overrides(instance):
	fields = instance.PERSIST
	for field in fields:
		if field.startswith("#"):
			continue
		argv_field = field.replace("_", "-")
		value = None
		enable_arg = "--%s" % argv_field
		if enable_arg in sys.argv:
			value = True
		disable_arg = "--no-%s" % argv_field
		if disable_arg in sys.argv:
			value = False
		set_arg = "--%s=" % argv_field
		for arg in sys.argv:
			if arg.startswith(set_arg):
				value = arg[len(set_arg):]
		if value is None:
			continue

		value_type = type(getattr(instance, field))
		if value_type is types.StringType or value_type is types.UnicodeType:
			if value:
				l = len(value)
				if l >= 2 and ((value[0]=='"' and value[l-1]=='"') or (value[0]=="'" and value[l-1]=="'")):
					value = value[1:l-1]
		elif value_type is types.BooleanType:
			value = get_bool(value)
		elif value_type is types.FloatType:
			value = float(value)
		elif value_type is types.IntType:
			value = int(value)
		elif value_type is types.LongType:
			value = long(value)
		else:
			logger.serror(" cannot handle command line argument for field %s of type %s" % (field, str(value_type)))
			continue

		logger.slog(" setting %s.%s=%s (type=%s)" % (instance, field, value, str(value_type)))
		setattr(instance, field, value)

def add_argv_usage(instance):
	for field in instance.PERSIST:
		if field.startswith("#"):
			continue
		value_type = type(getattr(instance, field))
		argv_field = field.replace("_", "-")
		if value_type is types.ListType:
			continue		#not handled yet
		if value_type is types.BooleanType:
			print("  --%s" % argv_field)
			print("  --no-%s" % argv_field)
		else:
			print("  --%s=VALUE" % argv_field)
