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

from __future__ import print_function, unicode_literals

import collections
import logging
import re
import subprocess
import sys
from itertools import chain

from portage import os
from portage.const import BASH_BINARY
from portage.output import red, green
from portage import _unicode_encode, _unicode_decode

from repoman._subprocess import repoman_getstatusoutput


_vcs_type = collections.namedtuple('_vcs_type', 'name dir_name')

_FindVCS_data = (
	_vcs_type(
		name='git',
		dir_name='.git'
	),
	_vcs_type(
		name='bzr',
		dir_name='.bzr'
	),
	_vcs_type(
		name='hg',
		dir_name='.hg'
	),
	_vcs_type(
		name='svn',
		dir_name='.svn'
	)
)


def FindVCS(cwd=None):
	"""
	Try to figure out in what VCS' working tree we are.

	@param cwd: working directory (default is os.getcwd())
	@type cwd: str
	@return: list of strings describing the discovered vcs types
	@rtype: list
	"""

	if cwd is None:
		cwd = os.getcwd()

	outvcs = []

	def seek(depth=None):
		""" Seek for VCSes that have a top-level data directory only. """
		retvcs = []
		pathprep = cwd

		while depth is None or depth > 0:
			for vcs_type in _FindVCS_data:
				vcs_dir = os.path.join(pathprep, vcs_type.dir_name)
				if os.path.isdir(vcs_dir):
					logging.debug(
						'FindVCS: found %(name)s dir: %(vcs_dir)s' % {
							'name': vcs_type.name,
							'vcs_dir': os.path.abspath(vcs_dir)})
					retvcs.append(vcs_type.name)

			if retvcs:
				break
			pathprep = os.path.join(pathprep, '..')
			if os.path.realpath(pathprep).strip('/') == '':
				break
			if depth is not None:
				depth = depth - 1

		return retvcs

	# Level zero VCS-es.
	if os.path.isdir(os.path.join(cwd, 'CVS')):
		outvcs.append('cvs')
	if os.path.isdir('.svn'):  # <1.7
		outvcs.append(os.path.join(cwd, 'svn'))

	# If we already found one of 'level zeros', just take a quick look
	# at the current directory. Otherwise, seek parents till we get
	# something or reach root.
	if outvcs:
		outvcs.extend(seek(1))
	else:
		outvcs = seek()

	if len(outvcs) > 1:
		# eliminate duplicates, like for svn in bug #391199
		outvcs = list(set(outvcs))

	return outvcs


def vcs_files_to_cps(vcs_file_iter, repolevel, reposplit, categories):
	"""
	Iterate over the given modified file paths returned from the vcs,
	and return a frozenset containing category/pn strings for each
	modified package.
	"""

	modified_cps = []

	if repolevel == 3:
		if reposplit[-2] in categories and \
			next(vcs_file_iter, None) is not None:
			modified_cps.append("/".join(reposplit[-2:]))

	elif repolevel == 2:
		category = reposplit[-1]
		if category in categories:
			for filename in vcs_file_iter:
				f_split = filename.split(os.sep)
				# ['.', pn, ...]
				if len(f_split) > 2:
					modified_cps.append(category + "/" + f_split[1])

	else:
		# repolevel == 1
		for filename in vcs_file_iter:
			f_split = filename.split(os.sep)
			# ['.', category, pn, ...]
			if len(f_split) > 3 and f_split[1] in categories:
				modified_cps.append("/".join(f_split[1:3]))

	return frozenset(modified_cps)


def vcs_new_changed(relative_path, mychanged, mynew):
	for x in chain(mychanged, mynew):
		if x == relative_path:
			return True
	return False


def git_supports_gpg_sign():
	status, cmd_output = \
		repoman_getstatusoutput("git --version")
	cmd_output = cmd_output.split()
	if cmd_output:
		version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
		if version is not None:
			version = [int(x) for x in version.groups()]
			if version[0] > 1 or \
				(version[0] == 1 and version[1] > 7) or \
				(version[0] == 1 and version[1] == 7 and version[2] >= 9):
				return True
	return False


def detect_vcs_conflicts(options, vcs):
	"""Determine if the checkout has problems like cvs conflicts.

	If you want more vcs support here just keep adding if blocks...
	This could be better.

	TODO(antarus): Also this should probably not call sys.exit() as
	repoman is run on >1 packages and one failure should not cause
	subsequent packages to fail.

	Args:
		vcs - A string identifying the version control system in use
	Returns:
		None (calls sys.exit on fatal problems)
	"""

	cmd = None
	if vcs == 'cvs':
		logging.info(
			"Performing a %s with a little magic grep to check for updates." %
			green("cvs -n up"))
		cmd = (
			"cvs -n up 2>/dev/null | "
			"egrep '^[^\?] .*' | "
			"egrep -v '^. .*/digest-[^/]+|^cvs server: .* -- ignored$'")
	if vcs == 'svn':
		logging.info(
			"Performing a %s with a little magic grep to check for updates." %
			green("svn status -u"))
		cmd = (
			"svn status -u 2>&1 | "
			"egrep -v '^.  +.*/digest-[^/]+' | "
			"head -n-1")

	if cmd is not None:
		# Use Popen instead of getstatusoutput(), in order to avoid
		# unicode handling problems (see bug #310789).
		args = [BASH_BINARY, "-c", cmd]
		args = [_unicode_encode(x) for x in args]
		proc = subprocess.Popen(
			args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
		out = _unicode_decode(proc.communicate()[0])
		proc.wait()
		mylines = out.splitlines()
		myupdates = []
		for line in mylines:
			if not line:
				continue

			# [ ] Unmodified (SVN)	[U] Updates		[P] Patches
			# [M] Modified			[A] Added		[R] Removed / Replaced
			# [D] Deleted
			if line[0] not in " UPMARD":
				# Stray Manifest is fine, we will readd it anyway.
				if line[0] == '?' and line[1:].lstrip() == 'Manifest':
					continue
				logging.error(red(
					"!!! Please fix the following issues reported "
					"from cvs: %s" % green("(U,P,M,A,R,D are ok)")))
				logging.error(red(
					"!!! Note: This is a pretend/no-modify pass..."))
				logging.error(out)
				sys.exit(1)
			elif vcs == 'cvs' and line[0] in "UP":
				myupdates.append(line[2:])
			elif vcs == 'svn' and line[8] == '*':
				myupdates.append(line[9:].lstrip(" 1234567890"))

		if myupdates:
			logging.info(green("Fetching trivial updates..."))
			if options.pretend:
				logging.info("(" + vcs + " update " + " ".join(myupdates) + ")")
				retval = os.EX_OK
			else:
				retval = os.system(vcs + " update " + " ".join(myupdates))
			if retval != os.EX_OK:
				logging.fatal("!!! " + vcs + " exited with an error. Terminating.")
				sys.exit(retval)


class VCSSettings(object):
	'''Holds various VCS settings'''

	def __init__(self, options=None, repoman_settings=None):
		if options.vcs:
			if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
				self.vcs = options.vcs
			else:
				self.vcs = None
		else:
			vcses = FindVCS()
			if len(vcses) > 1:
				print(red(
					'*** Ambiguous workdir -- more than one VCS found'
					' at the same depth: %s.' % ', '.join(vcses)))
				print(red(
					'*** Please either clean up your workdir'
					' or specify --vcs option.'))
				sys.exit(1)
			elif vcses:
				self.vcs = vcses[0]
			else:
				self.vcs = None

		if options.if_modified == "y" and self.vcs is None:
			logging.info(
				"Not in a version controlled repository; "
				"disabling --if-modified.")
			options.if_modified = "n"

		# Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
		self.vcs_preserves_mtime = self.vcs in ('cvs',)

		self.vcs_local_opts = repoman_settings.get(
			"REPOMAN_VCS_LOCAL_OPTS", "").split()
		self.vcs_global_opts = repoman_settings.get(
			"REPOMAN_VCS_GLOBAL_OPTS")
		if self.vcs_global_opts is None:
			if self.vcs in ('cvs', 'svn'):
				self.vcs_global_opts = "-q"
			else:
				self.vcs_global_opts = ""
		self.vcs_global_opts = self.vcs_global_opts.split()

		if options.mode == 'commit' and not options.pretend and not self.vcs:
			logging.info(
				"Not in a version controlled repository; "
				"enabling pretend mode.")
			options.pretend = True
