# Copyright 2010 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

__all__ = (
	'features_set',
)

import logging

from portage.const import SUPPORTED_FEATURES
from portage.localization import _
from portage.output import colorize
from portage.util import writemsg_level

class features_set(object):
	"""
	Provides relevant set operations needed for access and modification of
	config.features. The FEATURES variable is automatically synchronized
	upon modification.

	Modifications result in a permanent override that will cause the change
	to propagate to the incremental stacking mechanism in config.regenerate().
	This eliminates the need to call config.backup_changes() when FEATURES
	is modified, since any overrides are guaranteed to persist despite calls
	to config.reset().
	"""

	def __init__(self, settings):
		self._settings = settings
		self._features = set()

	def __contains__(self, k):
		return k in self._features

	def __iter__(self):
		return iter(self._features)

	def _sync_env_var(self):
		self._settings['FEATURES'] = ' '.join(sorted(self._features))

	def add(self, k):
		self._settings.modifying()
		self._settings._features_overrides.append(k)
		if k not in self._features:
			self._features.add(k)
			self._sync_env_var()

	def update(self, values):
		self._settings.modifying()
		values = list(values)
		self._settings._features_overrides.extend(values)
		need_sync = False
		for k in values:
			if k in self._features:
				continue
			self._features.add(k)
			need_sync = True
		if need_sync:
			self._sync_env_var()

	def difference_update(self, values):
		self._settings.modifying()
		values = list(values)
		self._settings._features_overrides.extend('-' + k for k in values)
		remove_us = self._features.intersection(values)
		if remove_us:
			self._features.difference_update(values)
			self._sync_env_var()

	def remove(self, k):
		"""
		This never raises KeyError, since it records a permanent override
		that will prevent the given flag from ever being added again by
		incremental stacking in config.regenerate().
		"""
		self.discard(k)

	def discard(self, k):
		self._settings.modifying()
		self._settings._features_overrides.append('-' + k)
		if k in self._features:
			self._features.remove(k)
			self._sync_env_var()

	def _validate(self):
		"""
		Implements unknown-features-warn and unknown-features-filter.
		"""
		if 'unknown-features-warn' in self._features:
			unknown_features = \
				self._features.difference(SUPPORTED_FEATURES)
			if unknown_features:
				unknown_features = unknown_features.difference(
					self._settings._unknown_features)
				if unknown_features:
					self._settings._unknown_features.update(unknown_features)
					writemsg_level(colorize("BAD",
						_("FEATURES variable contains unknown value(s): %s") % \
						", ".join(sorted(unknown_features))) \
						+ "\n", level=logging.WARNING, noiselevel=-1)

		if 'unknown-features-filter' in self._features:
			unknown_features = \
				self._features.difference(SUPPORTED_FEATURES)
			if unknown_features:
				self.difference_update(unknown_features)
				self._prune_overrides()

	def _prune_overrides(self):
		"""
		If there are lots of invalid package.env FEATURES settings
		then unknown-features-filter can make _features_overrides
		grow larger and larger, so prune it. This performs incremental
		stacking with preservation of negative values since they need
		to persist for future config.regenerate() calls.
		"""
		overrides_set = set(self._settings._features_overrides)
		positive = set()
		negative = set()
		for x in self._settings._features_overrides:
			if x[:1] == '-':
				positive.discard(x[1:])
				negative.add(x[1:])
			else:
				positive.add(x)
				negative.discard(x)
		self._settings._features_overrides[:] = \
			list(positive) + list('-' + x for x in negative)
