#!/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 os
import sys
import traceback
import threading
import datetime

from winswitch.util.daemonize import is_daemonized

loggers=[]					#keep track of all the loggers we instantiate
logdir=None					#where we place the log files
logfilename = "winswitch-%Y%m%d-%H%M%S.log"		#default name of the logfile
							#may also be a full_path, in which case logdir is ignored
logbuffer=[]				#for when we buffer output (careful with that!)
default_debug=True

log_file = None				#the actual file object we write to
log_filepath = None			#the full path to the log file
lock=threading.Lock()

OLD_LOG = "winswitch.log.old"
DEBUG_IMPORTS = False or "--debug-imports" in sys.argv	#Useful for py2app/py2exe silent crashes (import fails and it just exits)
FORCE_DEBUG = False
WIN32 = sys.platform.startswith("win")

ansi_tty=False
try:
	ansi_tty = sys.stdout.isatty() and not WIN32
except:
	pass
log_to_file=WIN32 or "--log-to-file" in sys.argv or is_daemonized()
log_to_tty=(not WIN32 or "--log-to-tty" in sys.argv) and not is_daemonized()
log_to_buffer=False
log_datetime=True
log_severity=True		#or not ansi_tty

if hasattr(sys, "frozen") and "--debug-frozen" not in sys.argv:
	sys.stderr = sys.stdout



def set_loggers_debug(enabled):
	global default_debug
	default_debug = enabled or FORCE_DEBUG
	for logger in loggers:
		logger._debug = enabled

def set_log_dir(_logdir):
	global logdir
	logdir = _logdir

def set_log_filename(_logfilename):
	global logfilename
	logfilename = _logfilename


def _generate_log_filename():
	global logdir, logfilename
	filename = datetime.datetime.now().strftime(logfilename)
	return get_log_filename(logdir, filename)

def get_log_filename(logdir, filename):
	"""
	Expands filename and logdir into a full path.

	We duplicate some of the code from util/file_io.py because we can't import stuff that would cause logging (recursive)
	"""
	def _add_log_path(log_path, dirname):
		if not dirname:
			return log_path
		new_path = os.path.join(log_path, dirname)
		if not os.path.exists(new_path):
			os.mkdir(new_path)
		return new_path
	if WIN32:
		#detect absolute paths:
		if len(filename)>3 and filename[1]==':' and filename[2]=='\\':
			return filename		#assume this is a valid fullpath already... [X]:\[.]*
		#UAC in vista onwards will not allow us to write where the software is installed, so place the log file in "~/Application Data"
		log_path = os.environ.get("APPDATA")
	else:
		#detect absolute paths:
		if filename.startswith("/"):
			return	filename
		log_path = os.path.expanduser("~")			#others (unix like): stick it $HOME
	# add ".winswitch" (or "Window Switch" on win32)
	from winswitch.util.paths import WINSWITCH_DIR
	log_path = _add_log_path(log_path, WINSWITCH_DIR)
	# add sub-directory (ie: "client" or "server")
	log_path = _add_log_path(log_path, logdir)
	# add actual log filename
	return os.path.join(log_path, filename)

log_file = None
def get_log_file():
	"""
	Returns the logfile.
	Creates it if needed.
	"""
	global log_file, log_filepath
	if not log_file:
		log_filepath = _generate_log_filename()
		if log_to_tty:
			print("simple_logger.get_log_file() opening %s" % log_filepath)
		log_file = open(log_filepath, "a")
	return	log_file

def rotate_log_file():
	global log_file, log_filepath, logdir
	try:
		try:
			lock.acquire()
			if log_file:
				log_file.close()
				log_file = None
			if log_filepath and os.path.exists(log_filepath):
				archive_path = get_log_filename(logdir, OLD_LOG)
				os.rename(log_filepath, archive_path)
				log_filepath = None
		finally:
			lock.release()
	except Exception, e:
		print("failed to rotate log file: %s" % e)


def all_to_file():
	global log_to_file
	global log_to_tty
	log_to_tty = False
	log_to_file = True
	lf = get_log_file()
	sys.stdout = lf
	sys.stderr = lf

def set_log_to_file(enabled):
	global log_to_file
	log_to_file = enabled

def set_log_to_tty(enabled):
	global log_to_tty
	log_to_tty = enabled

def set_log_to_buffer(enabled):
	global log_to_buffer
	log_to_buffer = enabled


def make_timestamp():
	if log_datetime:
		return	datetime.datetime.now().strftime("%Y/%d/%m %H:%M:%S.%f ")
	return	""


def tty_log(colour, severity, ts, prefix, msg):
	if ansi_tty:
		print("\033[1;%dm%s%s%s%s\033[1;m" % (colour, severity, ts, prefix, msg))
	else:
		print("%s%s%s%s" % (severity, ts, prefix, msg))

def file_log(colour, severity, ts, prefix, msg):
	lf = get_log_file()
	lf.write("%s%s%s%s\n" % (severity, ts, prefix, msg))
	lf.flush()

def write_log(colour, severity, prefix, msg):
	global lock, log_severity
	ts = make_timestamp()
	if not log_severity:
		severity=""
	else:
		severity="[%s] " % severity
	try:
		lock.acquire()
		if log_to_tty:
			tty_log(colour, severity, ts, prefix, msg)
		if log_to_file:
			file_log(colour, severity, ts, prefix, msg)
		if log_to_buffer:
			logbuffer.append((colour, severity, ts, prefix, msg))
	finally:
		lock.release()

def flush_logbuffer():
	global lock
	try:
		lock.acquire()
		for (colour, ts, prefix, msg) in logbuffer:
			if log_to_tty:
				tty_log(colour, ts, prefix, msg)
			if log_to_file:
				file_log(colour, ts, prefix, msg)
	finally:
		lock.release()
		logbuffer = []


def dump_exception(colour, exception):
	#traceback.print_exc()
	t, value, tb = sys.exc_info()
	stack = traceback.format_exception(t, value, tb)
	if stack:
		for line in stack:
			for sl in line.splitlines():
				write_log(colour, "ee", "", sl)


def msig(*args):
	"""
	Used to create a method signature string from a list of arguments.
	ie: msig('a', 123)="(a,123)"
	"""
	if len(args)==0:
		return	"()"
	try:
		return	"("+(','.join([str(v) for v in args]))+")"
	except UnicodeDecodeError:
		#Slow version: catch decode error on individual arg and ignore it..
		s = None
		for arg in args:
			try:
				strarg=str(arg)
			except UnicodeDecodeError, e:
				strarg="%s on %s" % (e, type(arg))
				#FIXME: use unicode decode whatever, just print the bloody thing...
			if not s:
				s = strarg
			else:
				s += "," + strarg
		return "(%s)" % s

def msgsig(msg, *args):
	s = msig(*args)
	if msg:
		m = str(msg)
		if not m.startswith("="):
			s += " "
		s += m
	return	s

class Logger:

	GRAY = 30
	RED = 31
	GREEN = 32
	YELLOW = 33
	BLUE = 34
	MAGENTA = 35
	CYAN = 36
	WHITE = 37
	CRIMSON = 38
	HIGHLIGHTED_RED = 41
	HIGHLIGHTED_BROWN = 43
	HIGHLIGHTED_BLUE = 44
	HIGHLIGHTED_MAGENTA = 45
	HIGHLIGHTED_CYAN = 46
	HIGHLIGHTED_CRIMSON = 48

	DEFAULT_LOG_COLOUR = GREEN
	DEFAULT_DEBUG_COLOUR = BLUE
	DEFAULT_ERROR_COLOUR = RED


	def __init__(self, prefix_or_class,
				log_colour=DEFAULT_LOG_COLOUR, debug_colour = DEFAULT_DEBUG_COLOUR,
				error_colour = DEFAULT_ERROR_COLOUR,
				_debug=None, register_methods=True):
		if type(prefix_or_class) == str:
			self.prefix = prefix_or_class
		else:
			self.prefix = prefix_or_class.__class__.__name__
			if register_methods:
				self.add_methods(prefix_or_class)
		if not self.prefix.endswith("."):
			self.prefix += "."
		if _debug is not None:
			self._debug = _debug
		else:
			self._debug = default_debug

		self.log_colour = log_colour
		self.debug_colour = debug_colour
		self.error_colour = error_colour
		global loggers
		loggers.append(self)

	def __str__(self):
		return	"Logger()"

	def add_methods(self, some_class):
		some_class.slog = self.slog
		some_class.log = self.log
		some_class.serr = self.serr
		some_class.error = self.error
		some_class.serror = self.serror
		some_class.sdebug = self.sdebug
		some_class.debug = self.debug
		some_class.exc = self.exc
		some_class.cwrite = self.cwrite
		some_class.logger = self
		return	self

	def cwrite(self, colour, severity, msg=None, *args):
		self._write(colour, severity, self.prefix, msgsig(msg, *args))

	def _write(self, colour, severity, prefix, msg):
		if not msg:
			msg = "()"
		if msg.startswith("("):
			try:
				fn_name = sys._getframe(2).f_code.co_name
			except:
				fn_name = ""
			msg = fn_name+msg
		msg = msg.replace("\n", "\\n")
		write_log(colour, severity, prefix, msg)

	def slog(self, msg=None, *args):
		self._write(self.log_colour, "II", self.prefix, msgsig(msg, *args))

	def log(self, msg=None):
		self._write(self.log_colour, "II", self.prefix, msg)

	def serr(self, msg=None, exception=None, *args):
		self._write(self.error_colour, "EE", self.prefix, msgsig(msg, *args))
		if exception:
			dump_exception(self.error_colour, exception)

	def serror(self, msg=None, *args):
		self._write(self.error_colour, "EE", self.prefix, msgsig(msg, *args))

	def error(self, msg=None, exception=None):
		self._write(self.error_colour, "EE", self.prefix, msg)
		if exception:
			dump_exception(self.error_colour, exception)

	def exc(self, exception):
		self._write(self.error_colour, "EE", self.prefix, None)
		if exception:
			dump_exception(self.error_colour, exception)

	def sdebug(self, msg=None, *args):
		if self._debug:
			self._write(self.debug_colour, "DD", self.prefix, msgsig(msg, *args))

	def debug(self, msg=None):
		if self._debug:
			self._write(self.debug_colour, "DD", self.prefix, msg)

	def get_debug_import(self, force=False):
		"""
		Utility method which returns a logging method which is a no-op unless DEBUG_IMPORTS or force is True
		"""
		if DEBUG_IMPORTS or force:
			return	self.slog
		else:
			def noop(*args):
				pass
			return	noop
