from itertools import chain

from ...types import Timestamp
from ...util import none_or

from . import defaults
from .functions import detect

"""
Simple constant used in order to not do weird things with a dummy revision.
"""
def check_row(db, rev_row, **kwargs):
	"""
	Checks whether a revision (database row) was reverted (identity) and returns 
	a named tuple of Revert(reverting, reverteds, reverted_to).
	
	:Parameters:
		db : :class:`mw.database.DB` 
			A database connection to make use of. 
		rev_row : dict
			a revision row containing 'rev_id' and 'rev_page' or 'page_id'
		radius : int
			the maximum number of revisions that can be reverted
		check_archive : bool
			should the archive table be check for reverting revisions?
		before : `Timestamp`
			if set, limits the search for *reverting* revisions to those which were saved before this timestamp
	"""
	
	# extract rev_id, sha1, page_id
	if 'rev_id' in rev_row: rev_id = rev_row['rev_id']
	else: raise TypeError("rev_row must have 'rev_id'")
	if 'page_id' in rev_row: page_id = rev_row['page_id']
	elif 'rev_page' in rev_row: page_id = rev_row['rev_page']
	else: raise TypeError("rev_row must have 'page_id' or 'rev_page'")
	
	# run the regular check
	return check(db, rev_id, page_id=page_id, **kwargs)
	


def check(db, rev_id, page_id=None, radius=defaults.RADIUS, check_archive=False, before=None):
	"""
	Checks whether a revision was reverted (identity) and returns a named tuple
	of Revert(reverting, reverteds, reverted_to).
	
	:Parameters:
		db : `mw.database.DB`
			A database connection to make use of. 
		rev_id : int
			the ID of the revision to check
		page_id : int
			the ID of the page the revision occupies (slower if not provided)
		radius : int
			the maximum number of revisions that can be reverted
		check_archive : bool
			should the archive table be check for reverting revisions?
		before : `Timestamp`
			if set, limits the search for *reverting* revisions to those which were saved before this timestamp
	"""
	
	if not hasattr(db, "revisions") and hasattr(db, "all_revisions"):
		raise TypeError("db wrong type.  Expected a mw.database.DB.")
	
	rev_id = int(rev_id)
	radius = int(radius)
	page_id = none_or(page_id, int)
	check_archive = bool(check_archive)
	before = none_or(before, Timestamp)
	
	# If we are searching the archive, we'll need to use `all_revisions`.
	if check_archive: dbrevs = db.all_revisions
	else: dbrevs = db.revisions
	
	# If we don't have the sha1 or page_id, we're going to need to look them up
	if page_id == None:
		row = dbrevs.get(id=rev_id)
		page_id = row['rev_page']
		
	# Load history and current rev
	current_and_past_revs = list(dbrevs.query(
		page_id=page_id,
		limit=radius+1,
		before_id=rev_id+1, # Ensures that we capture the current revision
		direction="older"
	))
	
	try:
		# Extract current rev and reorder history
		current_rev, past_revs = (
			current_and_past_revs[0], # Current rev is the first one returned
			reversed(current_and_past_revs[1:]) # The rest are past revs, but they are in the wrong order
		)
	except IndexError:
		# Only way to get here is if there isn't enough history.  Couldn't be 
		# reverted.  Just return None. 
		return None
	
	# Load future revisions
	future_revs = dbrevs.query(
		page_id=page_id,
		limit=radius,
		after_id=rev_id,
		before=before,
		direction="newer"
	)
	
	# Convert to an iterable of (checksum, rev) pairs for detect() to consume
	checksum_revisions = chain(
		((rev['rev_sha1'], rev) for rev in past_revs),
		[(current_rev['rev_sha1'], current_rev)],
		((rev['rev_sha1'], rev) for rev in future_revs)
	)
	
	for revert in detect(checksum_revisions, radius=radius):
		# Check that this is a relevant revert
		if rev_id in [rev['rev_id'] for rev in revert.reverteds]:
			return revert
		
	return None
