from parallels.common import messages
from collections import defaultdict
from parallels.common.utils.entity import Entity
from parallels.common.utils.yaml_utils import write_yaml
from parallels.utils import find_first, default, write_unicode_file
from threading import Lock


class MigrationProgress(object):
	def __init__(self, status_text_filename, status_yaml_filename):
		"""
		:type status_text_filename: basestring
		:type status_yaml_filename: basestring
		"""
		self._subscriptions = []
		self._status_text_filename = status_text_filename
		self._status_yaml_filename = status_yaml_filename
		self._file_write_lock = Lock()
		self.write_initial()

	def write_initial(self):
		"""
		:rtype: None
		"""
		initial_message = messages.PERSUBSCRIPTION_MIGRATION_WAS_NOT_STARTED_YET
		with self._file_write_lock:
			write_unicode_file(self._status_text_filename, initial_message)
			write_yaml(self._status_yaml_filename, self._subscriptions)

	def write_status(self):
		"""
		:rtype: None
		"""
		with self._file_write_lock:
			progress_file_text = ''
			# collect stats
			statuses = {}
			for status in SubscriptionMigrationStatus.all():
				statuses[status] = len([s for s in self._subscriptions if s.status == status])
			total = len(self._subscriptions)
			finished = len([s for s in self._subscriptions if s.status in SubscriptionMigrationStatus.finished()])
			not_finished = total - finished

			progress_file_text += 'Summary:\n'
			summary_table = list()
			summary_table.append([
				'Total', total,
				'Finished', self.format_percentage(finished, total),
				'Not finished', self.format_percentage(not_finished, total)
			])
			progress_file_text += self.format_table(summary_table)

			progress_file_text += 'Not finished:\n'
			not_finished = sum([
				[
					SubscriptionMigrationStatus.to_string(status),
					self.format_percentage(statuses[status], total)
				]
				for status in SubscriptionMigrationStatus.not_finished()
			], [])
			progress_file_text += self.format_table([not_finished])

			progress_file_text += 'Finished:\n'
			finished_table = sum([
				[
					SubscriptionMigrationStatus.to_string(status),
					self.format_percentage(statuses[status], total)
				]
				for status in SubscriptionMigrationStatus.finished()
			], [])
			progress_file_text += self.format_table([finished_table]) + '\n'

			progress_file_text += messages.SUBSCRIPTION_MIGRATION_STATUS
			subscriptions_table = list()
			subscriptions_table.append(['Subscription', 'Status', 'Action'])
			for s in self._subscriptions:
				subscriptions_table.append([
					s.name, SubscriptionMigrationStatus.to_string(s.status), default(s.action, '')
				])

			progress_file_text += self.format_table(subscriptions_table)

			write_unicode_file(self._status_text_filename, progress_file_text)
			write_yaml(self._status_yaml_filename, self._subscriptions)

	@staticmethod
	def format_percentage(numerator, denominator):
		"""
		:type numerator: int
		:type denominator: int
		:rtype: basestring
		"""
		if denominator is None or denominator == 0:
			return 'N/A%'
		else:
			percentage = (float(numerator) / float(denominator)) * 100.0
			return "%s (%.0f%%)" % (numerator, percentage)

	@staticmethod
	def format_table(rows, h_padding=2):
		"""
		:type rows: list[list]
		:type h_padding: int
		:rtype: basestring
		"""
		if len(rows) == 0:
			return ''

		cols = defaultdict(list)

		for row in rows:
			for col_num, val in enumerate(row):
				cols[col_num].append(val)

		max_col_length = {
			col_num: max(len(unicode(val)) for val in col)
			for col_num, col in cols.iteritems()
		}

		border_row = "+%s+\n" % (
			'+'.join([
				'-' * (max_len + h_padding)
				for max_len in max_col_length.values()
			])
		)

		result = ''
		result += border_row

		row_strings = []
		for row in rows:
			row_strings.append("|%s|\n" % (
				'|'.join([
					unicode(value).center(max_col_length[col_num] + h_padding)
					for col_num, value in enumerate(row)
				])
			))

		result += border_row.join(row_strings)

		result += border_row

		return result

	def get_subscription(self, name):
		"""
		:type name: basestring
		:rtype: parallels.common.utils.migration_progress.SubscriptionMigrationProgress
		"""
		s = find_first(self._subscriptions, lambda s: s.name == name)
		if s is None:
			s = SubscriptionMigrationProgress(
				name=name, status=SubscriptionMigrationStatus.NOT_STARTED, action=None,
				on_change=self.write_status
			)
			self._subscriptions.append(s)
		return s


class SubscriptionMigrationStatus(object):
	NOT_STARTED = 'not-started'
	IN_PROGRESS = 'in-progress'
	ON_HOLD = 'on-hold'
	FINISHED_OK = 'finished-ok'
	FINISHED_WARNINGS = 'finished-warnings'
	FINISHED_ERRORS = 'finished-errors'

	@classmethod
	def all(cls):
		"""
		:rtype: list[basestring]
		"""
		return [
			cls.NOT_STARTED,
			cls.IN_PROGRESS,
			cls.ON_HOLD,
			cls.FINISHED_OK,
			cls.FINISHED_WARNINGS,
			cls.FINISHED_ERRORS,
		]

	@classmethod
	def not_finished(cls):
		"""
		:rtype: list[basestring]
		"""
		return [
			cls.NOT_STARTED,
			cls.IN_PROGRESS,
			cls.ON_HOLD,
		]

	@classmethod
	def finished(cls):
		"""
		:rtype: list[basestring]
		"""
		return [
			cls.FINISHED_OK,
			cls.FINISHED_WARNINGS,
			cls.FINISHED_ERRORS,
		]

	@classmethod
	def to_string(cls, state):
		"""
		:type state: basestring
		:rtype: basestring
		"""
		return {
			cls.NOT_STARTED: u'Not Started',
			cls.IN_PROGRESS: u'In Progress',
			cls.ON_HOLD: u'On Hold',
			cls.FINISHED_OK: u'Finished unsuccessfully',
			cls.FINISHED_WARNINGS: u'Finished with warnings',
			cls.FINISHED_ERRORS: u'Failed',
		}.get(state, state)


class SubscriptionMigrationProgress(Entity):
	def __init__(self, name, status, action, on_change):
		"""
		:type name: basestring
		:type status: basestring
		:type action: basestring | None
		:type on_change: () -> None
		:return:
		"""
		self._name = name
		self._status = status
		self._action = action
		self._on_change = on_change

	@property
	def name(self):
		"""
		:rtype: basestring
		"""
		return self._name

	@property
	def status(self):
		"""
		:rtype: basestring
		"""
		return self._status

	@status.setter
	def status(self, new_status):
		"""
		:type new_status: basestring
		:rtype: None
		"""
		self._status = new_status
		self._on_change()

	@property
	def action(self):
		"""
		:rtype: basestring
		"""
		return self._action

	@action.setter
	def action(self, new_action):
		"""
		:type new_action: basestring
		:rtype: None
		"""
		self._action = new_action
		self._on_change()
