import threading
from contextlib import contextmanager
import logging
import time
from parallels.core.utils.common import default

thread_local = threading.local()


@contextmanager
def log_context(name):
	init_thread()
	thread_local.current_context.append(unicode(name))
	thread_local.current_context_str = '/'.join(
		thread_local.current_context
	)
	try:
		yield
	except Exception as e:
		if not hasattr(e, 'context'):
			e.context = thread_local.current_context_str
		raise
	finally:
		thread_local.current_context.pop()
		thread_local.current_context_str = '/'.join(
			thread_local.current_context
		)


@contextmanager
def subscription_context(subscription_name):
	"""Context manager inside which it is considered that we are working with specified subscription"""
	init_thread()
	if thread_local.current_subscription_name == subscription_name:
		yield
	else:
		thread_local.current_subscription_name = subscription_name
		try:
			yield
		finally:
			thread_local.current_subscription_name = None


class DebugLogFormatter(logging.Formatter):
	"""Log formatter for migrator's debug log"""

	def __init__(self):
		super(DebugLogFormatter, self).__init__(
			fmt=(
				"%(asctime)s|%(levelname_brief)s|%(thread_name_brief)s|"
				"%(name_brief)s|%(subscription)s|%(context)s|%(message)s"
			),
			datefmt='%Y-%m-%d_%H:%M:%S,%03d'
		)

	def format(self, record):
		result = logging.Formatter.format(self, record)
		if record.message:
			# dirty way to split formatted message by
			# head (time, level, thread name, etc) and tail (message text)
			parts = result.split(record.message)
			if len(parts) == 2:
				head = parts[0]
				result = result.replace('\n', '\n' + head)

		lines = result.split('\n')
		# add '[+]' for each start line, '[=]' for each continuation line
		result = '\n'.join(['+|' + lines[0]] + ['=|' + line for line in lines[1:]])

		return result

	def formatTime(self, record, datefmt=None):
		ct = self.converter(record.created)
		# use '_' as a delimiter between time and date so we could use cut/awk
		# without complex expressions, just splitting log line by spaces
		t = time.strftime("%Y-%m-%d_%H:%M:%S", ct)
		s = "%s,%03d" % (t, record.msecs)
		return s


class InfoLogFormatter(logging.Formatter):
	"""Log formatter for migrator's info log and console output"""

	def __init__(self):
		fmt = "[%(levelname)s]%(subscription_context)s %(message)s"
		super(InfoLogFormatter, self).__init__(fmt)

	def format(self, record):
		# include subscription name if we are in subscription context,
		# otherwise do not include anything else,
		# even empty '[]', to keep info log and console output brief
		if record.subscription != '':
			record.subscription_context = ' [%s]' % record.subscription
		else:
			record.subscription_context = ''
		result = logging.Formatter.format(self, record)
		return result


class IndentFilter(logging.Filter):
	"""Add additional "context" information to every log record"""
	def filter(self, record):
		init_thread()
		record.context = thread_local.current_context_str
		record.subscription = default(thread_local.current_subscription_name, '')
		record.indent = len(thread_local.current_context) * '  '

		if record.threadName == 'MainThread':
			record.thread_name_brief = 'MT'
		elif record.threadName.startswith('SubscriptionThread'):
			record.thread_name_brief = 'ST%s' % (record.threadName[len('SubscriptionThread'):])
		else:
			record.thread_name_brief = record.threadName

		if record.levelname is not None and len(record.levelname) > 0:
			record.levelname_brief = record.levelname[0]
		else:
			record.levelname_brief = ''

		if record.name is not None and record.name.startswith('parallels.'):
			record.name_brief = record.name[len('parallels.'):]
		else:
			record.name_brief = record.name

		return True


def init_thread():
	current_context = getattr(thread_local, 'current_context', None)
	if current_context is None:
		thread_local.current_context = []
		thread_local.current_subscription_name = None

	current_context_str = getattr(thread_local, 'current_context_str', None)
	if current_context_str is None:
		thread_local.current_context_str = ''