# -*- coding:utf-8 -*-

import logging

from _emerge.Package import Package

# import our initialized portage instance
from repoman._portage import portage

max_desc_len = 80
allowed_filename_chars = "a-zA-Z0-9._-+:"

qahelp = {
	"CVS/Entries.IO_error": (
		"Attempting to commit, and an IO error was encountered access the"
		" Entries file"),
	"ebuild.invalidname": (
		"Ebuild files with a non-parseable or syntactically incorrect name"
		" (or using 2.1 versioning extensions)"),
	"ebuild.namenomatch": (
		"Ebuild files that do not have the same name as their parent"
		" directory"),
	"changelog.ebuildadded": (
		"An ebuild was added but the ChangeLog was not modified"),
	"changelog.missing": (
		"Missing ChangeLog files"),
	"ebuild.notadded": (
		"Ebuilds that exist but have not been added to cvs"),
	"ebuild.patches": (
		"PATCHES variable should be a bash array to ensure white space safety"),
	"changelog.notadded": (
		"ChangeLogs that exist but have not been added to cvs"),
	"dependency.bad": (
		"User-visible ebuilds with unsatisfied dependencies"
		" (matched against *visible* ebuilds)"),
	"dependency.badmasked": (
		"Masked ebuilds with unsatisfied dependencies"
		" (matched against *all* ebuilds)"),
	"dependency.badindev": (
		"User-visible ebuilds with unsatisfied dependencies"
		" (matched against *visible* ebuilds) in developing arch"),
	"dependency.badmaskedindev": (
		"Masked ebuilds with unsatisfied dependencies"
		" (matched against *all* ebuilds) in developing arch"),
	"dependency.badtilde": (
		"Uses the ~ dep operator with a non-zero revision part,"
		" which is useless (the revision is ignored)"),
	"dependency.missingslot": (
		"RDEPEND matches more than one SLOT but does not specify a "
		"slot and/or use the := or :* slot operator"),
	"dependency.perlcore": (
		"This ebuild directly depends on a package in perl-core;"
		" it should use the corresponding virtual instead."),
	"dependency.syntax": (
		"Syntax error in dependency string"
		" (usually an extra/missing space/parenthesis)"),
	"dependency.unknown": (
		"Ebuild has a dependency that refers to an unknown package"
		" (which may be valid if it is a blocker for a renamed/removed package,"
		" or is an alternative choice provided by an overlay)"),
	"file.executable": (
		"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need"
		" the executable bit"),
	"file.size": (
		"Files in the files directory must be under 20 KiB"),
	"file.size.fatal": (
		"Files in the files directory must be under 60 KiB"),
	"file.name": (
		"File/dir name must be composed"
		" of only the following chars: %s " % allowed_filename_chars),
	"file.UTF8": (
		"File is not UTF8 compliant"),
	"inherit.deprecated": (
		"Ebuild inherits a deprecated eclass"),
	"inherit.missing": (
		"Ebuild uses functions from an eclass but does not inherit it"),
	"inherit.unused": (
		"Ebuild inherits an eclass but does not use it"),
	"java.eclassesnotused": (
		"With virtual/jdk in DEPEND you must inherit a java eclass"),
	"wxwidgets.eclassnotused": (
		"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass"),
	"KEYWORDS.dropped": (
		"Ebuilds that appear to have dropped KEYWORDS for some arch"),
	"KEYWORDS.missing": (
		"Ebuilds that have a missing or empty KEYWORDS variable"),
	"KEYWORDS.stable": (
		"Ebuilds that have been added directly with stable KEYWORDS"),
	"KEYWORDS.stupid": (
		"Ebuilds that use KEYWORDS=-* instead of package.mask"),
	"LICENSE.missing": (
		"Ebuilds that have a missing or empty LICENSE variable"),
	"LICENSE.virtual": (
		"Virtuals that have a non-empty LICENSE variable"),
	"DESCRIPTION.missing": (
		"Ebuilds that have a missing or empty DESCRIPTION variable"),
	"DESCRIPTION.toolong": (
		"DESCRIPTION is over %d characters" % max_desc_len),
	"EAPI.definition": (
		"EAPI definition does not conform to PMS section 7.3.1"
		" (first non-comment, non-blank line)"),
	"EAPI.deprecated": (
		"Ebuilds that use features that are deprecated in the current EAPI"),
	"EAPI.incompatible": (
		"Ebuilds that use features that are only available with a different"
		" EAPI"),
	"EAPI.unsupported": (
		"Ebuilds that have an unsupported EAPI version"
		" (you must upgrade portage)"),
	"SLOT.invalid": (
		"Ebuilds that have a missing or invalid SLOT variable value"),
	"HOMEPAGE.missing": (
		"Ebuilds that have a missing or empty HOMEPAGE variable"),
	"HOMEPAGE.virtual": (
		"Virtuals that have a non-empty HOMEPAGE variable"),
	"PDEPEND.suspect": (
		"PDEPEND contains a package that usually only belongs in DEPEND."),
	"LICENSE.syntax": (
		"Syntax error in LICENSE"
		" (usually an extra/missing space/parenthesis)"),
	"PROVIDE.syntax": (
		"Syntax error in PROVIDE"
		" (usually an extra/missing space/parenthesis)"),
	"PROPERTIES.syntax": (
		"Syntax error in PROPERTIES"
		" (usually an extra/missing space/parenthesis)"),
	"RESTRICT.syntax": (
		"Syntax error in RESTRICT"
		" (usually an extra/missing space/parenthesis)"),
	"REQUIRED_USE.syntax": (
		"Syntax error in REQUIRED_USE"
		" (usually an extra/missing space/parenthesis)"),
	"SRC_URI.syntax": (
		"Syntax error in SRC_URI"
		" (usually an extra/missing space/parenthesis)"),
	"SRC_URI.mirror": (
		"A uri listed in profiles/thirdpartymirrors is found in SRC_URI"),
	"ebuild.syntax": (
		"Error generating cache entry for ebuild;"
		" typically caused by ebuild syntax error"
		" or digest verification failure"),
	"ebuild.output": (
		"A simple sourcing of the ebuild produces output;"
		" this breaks ebuild policy."),
	"ebuild.nesteddie": (
		"Placing 'die' inside ( ) prints an error,"
		" but doesn't stop the ebuild."),
	"variable.invalidchar": (
		"A variable contains an invalid character"
		" that is not part of the ASCII character set"),
	"variable.readonly": (
		"Assigning a readonly variable"),
	"variable.usedwithhelpers": (
		"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers"),
	"LIVEVCS.stable": (
		"This ebuild is a live checkout from a VCS but has stable keywords."),
	"LIVEVCS.unmasked": (
		"This ebuild is a live checkout from a VCS but has keywords"
		" and is not masked in the global package.mask."),
	"IUSE.invalid": (
		"This ebuild has a variable in IUSE"
		" that is not in the use.desc or its metadata.xml file"),
	"IUSE.missing": (
		"This ebuild has a USE conditional"
		" which references a flag that is not listed in IUSE"),
	"IUSE.rubydeprecated": (
		"The ebuild has set a ruby interpreter in USE_RUBY,"
		" that is not available as a ruby target anymore"),
	"LICENSE.invalid": (
		"This ebuild is listing a license"
		" that doesnt exist in portages license/ dir."),
	"LICENSE.deprecated": (
		"This ebuild is listing a deprecated license."),
	"KEYWORDS.invalid": (
		"This ebuild contains KEYWORDS"
		" that are not listed in profiles/arch.list"
		" or for which no valid profile was found"),
	"RDEPEND.implicit": (
		"RDEPEND is unset in the ebuild"
		" which triggers implicit RDEPEND=$DEPEND assignment"
		" (prior to EAPI 4)"),
	"RDEPEND.suspect": (
		"RDEPEND contains a package that usually only belongs in DEPEND."),
	"RESTRICT.invalid": (
		"This ebuild contains invalid RESTRICT values."),
	"digest.assumed": (
		"Existing digest must be assumed correct (Package level only)"),
	"digest.missing": (
		"Some files listed in SRC_URI aren't referenced in the Manifest"),
	"digest.unused": (
		"Some files listed in the Manifest aren't referenced in SRC_URI"),
	"ebuild.majorsyn": (
		"This ebuild has a major syntax error"
		" that may cause the ebuild to fail partially or fully"),
	"ebuild.minorsyn": (
		"This ebuild has a minor syntax error"
		" that contravenes gentoo coding style"),
	"ebuild.badheader": (
		"This ebuild has a malformed header"),
	"manifest.bad": (
		"Manifest has missing or incorrect digests"),
	"metadata.missing": (
		"Missing metadata.xml files"),
	"metadata.bad": (
		"Bad metadata.xml files"),
	"metadata.warning": (
		"Warnings in metadata.xml files"),
	"portage.internal": (
		"The ebuild uses an internal Portage function or variable"),
	"repo.eapi.banned": (
		"The ebuild uses an EAPI which is"
		" banned by the repository's metadata/layout.conf settings"),
	"repo.eapi.deprecated": (
		"The ebuild uses an EAPI which is"
		" deprecated by the repository's metadata/layout.conf settings"),
	"virtual.oldstyle": (
		"The ebuild PROVIDEs an old-style virtual (see GLEP 37)"),
	"virtual.suspect": (
		"Ebuild contains a package"
		" that usually should be pulled via virtual/, not directly."),
	"usage.obsolete": (
		"The ebuild makes use of an obsolete construct"),
	"upstream.workaround": (
		"The ebuild works around an upstream bug,"
		" an upstream bug should be filed and tracked in bugs.gentoo.org")
}

qacats = list(qahelp)
qacats.sort()

qawarnings = set((
	"changelog.missing",
	"changelog.notadded",
	"dependency.unknown",
	"digest.assumed",
	"digest.unused",
	"ebuild.notadded",
	"ebuild.nesteddie",
	"dependency.badmasked",
	"dependency.badindev",
	"dependency.badmaskedindev",
	"dependency.badtilde",
	"dependency.missingslot",
	"dependency.perlcore",
	"DESCRIPTION.toolong",
	"EAPI.deprecated",
	"HOMEPAGE.virtual",
	"LICENSE.deprecated",
	"LICENSE.virtual",
	"KEYWORDS.dropped",
	"KEYWORDS.stupid",
	"KEYWORDS.missing",
	"PDEPEND.suspect",
	"RDEPEND.implicit",
	"RDEPEND.suspect",
	"virtual.suspect",
	"RESTRICT.invalid",
	"ebuild.minorsyn",
	"ebuild.badheader",
	"ebuild.patches",
	"file.size",
	"inherit.unused",
	"inherit.deprecated",
	"java.eclassesnotused",
	"wxwidgets.eclassnotused",
	"metadata.warning",
	"portage.internal",
	"repo.eapi.deprecated",
	"usage.obsolete",
	"upstream.workaround",
	"LIVEVCS.stable",
	"LIVEVCS.unmasked",
	"IUSE.rubydeprecated",
))


missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
allvars.update(Package.metadata_keys)
allvars = sorted(allvars)

for x in missingvars:
	x += ".missing"
	if x not in qacats:
		logging.warning('* missingvars values need to be added to qahelp ("%s")' % x)
		qacats.append(x)
		qawarnings.add(x)

valid_restrict = frozenset([
	"binchecks", "bindist", "fetch", "installsources", "mirror",
	"preserve-libs", "primaryuri", "splitdebug", "strip", "test", "userpriv"])


suspect_rdepend = frozenset([
	"app-arch/cabextract",
	"app-arch/rpm2targz",
	"app-doc/doxygen",
	"dev-lang/nasm",
	"dev-lang/swig",
	"dev-lang/yasm",
	"dev-perl/extutils-pkgconfig",
	"dev-util/byacc",
	"dev-util/cmake",
	"dev-util/ftjam",
	"dev-util/gperf",
	"dev-util/gtk-doc",
	"dev-util/gtk-doc-am",
	"dev-util/intltool",
	"dev-util/jam",
	"dev-util/pkg-config-lite",
	"dev-util/pkgconf",
	"dev-util/pkgconfig",
	"dev-util/pkgconfig-openbsd",
	"dev-util/scons",
	"dev-util/unifdef",
	"dev-util/yacc",
	"media-gfx/ebdftopcf",
	"sys-apps/help2man",
	"sys-devel/autoconf",
	"sys-devel/automake",
	"sys-devel/bin86",
	"sys-devel/bison",
	"sys-devel/dev86",
	"sys-devel/flex",
	"sys-devel/m4",
	"sys-devel/pmake",
	"virtual/linux-sources",
	"virtual/pkgconfig",
	"x11-misc/bdftopcf",
	"x11-misc/imake",
])

suspect_virtual = {
	"dev-util/pkg-config-lite": "virtual/pkgconfig",
	"dev-util/pkgconf": "virtual/pkgconfig",
	"dev-util/pkgconfig": "virtual/pkgconfig",
	"dev-util/pkgconfig-openbsd": "virtual/pkgconfig",
	"dev-libs/libusb": "virtual/libusb",
	"dev-libs/libusbx": "virtual/libusb",
	"dev-libs/libusb-compat": "virtual/libusb",
}

ruby_deprecated = frozenset([
	"ruby_targets_ree18",
	"ruby_targets_ruby18",
	"ruby_targets_ruby19",
])


# file.executable
no_exec = frozenset(["Manifest", "ChangeLog", "metadata.xml"])


def format_qa_output(
	formatter, fails, dofull, dofail, options, qawarnings):
	"""Helper function that formats output properly

	@param formatter: an instance of Formatter
	@type formatter: Formatter
	@param fails: dict of qa status failures
	@type fails: dict
	@param dofull: Whether to print full results or a summary
	@type dofull: boolean
	@param dofail: Whether failure was hard or soft
	@type dofail: boolean
	@param options: The command-line options provided to repoman
	@type options: Namespace
	@param qawarnings: the set of warning types
	@type qawarnings: set
	@return: None (modifies formatter)
	"""
	full = options.mode == 'full'
	# we only want key value pairs where value > 0
	for category in sorted(fails):
		number = len(fails[category])
		formatter.add_literal_data("  " + category)
		spacing_width = 30 - len(category)
		if category in qawarnings:
			formatter.push_style("WARN")
		else:
			formatter.push_style("BAD")
			formatter.add_literal_data(" [fatal]")
			spacing_width -= 8

		formatter.add_literal_data(" " * spacing_width)
		formatter.add_literal_data("%s" % number)
		formatter.pop_style()
		formatter.add_line_break()
		if not dofull:
			if not full and dofail and category in qawarnings:
				# warnings are considered noise when there are failures
				continue
			fails_list = fails[category]
			if not full and len(fails_list) > 12:
				fails_list = fails_list[:12]
			for failure in fails_list:
				formatter.add_literal_data("   " + failure)
				formatter.add_line_break()


def format_qa_output_column(
	formatter, fails, dofull, dofail, options, qawarnings):
	"""Helper function that formats output in a machine-parseable column format

	@param formatter: an instance of Formatter
	@type formatter: Formatter
	@param fails: dict of qa status failures
	@type fails: dict
	@param dofull: Whether to print full results or a summary
	@type dofull: boolean
	@param dofail: Whether failure was hard or soft
	@type dofail: boolean
	@param options: The command-line options provided to repoman
	@type options: Namespace
	@param qawarnings: the set of warning types
	@type qawarnings: set
	@return: None (modifies formatter)
	"""
	full = options.mode == 'full'
	for category in sorted(fails):
		number = len(fails[category])
		formatter.add_literal_data("NumberOf " + category + " ")
		if category in qawarnings:
			formatter.push_style("WARN")
		else:
			formatter.push_style("BAD")
		formatter.add_literal_data("%s" % number)
		formatter.pop_style()
		formatter.add_line_break()
		if not dofull:
			if not full and dofail and category in qawarnings:
				# warnings are considered noise when there are failures
				continue
			fails_list = fails[category]
			if not full and len(fails_list) > 12:
				fails_list = fails_list[:12]
			for failure in fails_list:
				formatter.add_literal_data(category + " " + failure)
				formatter.add_line_break()
