// Copyright (c) 2010, Jens Peter Secher <jpsecher@gmail.com>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

class Mercurial
{
	public static function exitIfNoWorkingDirectory( directory : String )
	{
		// Check for presence of Mercurial directory.
		if( ! FileUtil.directoryExists( directory + "/.hg" ) )
		{
			Util.die
			(
				"Directory " + directory +
				" is not a valid Mercurial working directory."
			);
		}
	}

	//
	// Returns true if there are uncommitted changes.
	//
	public static function uncommittedChanges( logger : ILog ) : Bool
	{
		// Check for presence of Mercurial directory.
		Mercurial.exitIfNoWorkingDirectory( "." );
		// Are there uncommitted changes?
		var output = Process.runButExitOnError( "hg", ["status"], logger );
		return Lambda.count( output ) > 0;
	}

	//
	// Aborts if we are not in a Mercurial directory or if there are uncommitted
	// changes.
	//
	public static function exitIfUncommittedChanges( logger : ILog )
	{
		if( uncommittedChanges( logger ) )
		{
			Util.die( "Working directory has uncommitted changes." );
		}
	}

	//
	// Returns the hash of the head of a specific branch, or null if it does not
	// exist.
	//
	public static function branchHash( branch : String, logger : ILog )
	: Null<String>
	{
		var output = Process.runButExitOnError( "hg", ["branches"], logger );
		for( line in output )
		{
			// Match eg. "upstream    13:d2c015a5bd8e (inactive)".
			var r = ~/^([^ ]+) +[0-9]+:([0-9A-F]+)/i;
			if( r.match( line ) )
			{
				if( branch == r.matched( 1 ) ) return r.matched( 2 );
			}
		}
		return null;
	}

	public static function branchExists( branch : String, logger : ILog ) : Bool
	{
		return ( Mercurial.branchHash( branch, logger ) != null );
	}

	//
	// Switch to a named branch, creating it if necessary.
	//
	public static function switchToBranch( branch : String, logger : ILog )
	: Iter<String>
	{
		// Do we have to create the branch?
		if( branchHash( branch, logger ) != null )
		{
			// No, just go to the branch.
			return Process.runButExitOnError( "hg", ["update",branch], logger );
		}
		Process.runButExitOnError( "hg", [ "update", "-r", "0" ], logger );
		Process.runButExitOnError( "hg", [ "branch", branch ], logger );
		var msg = "Created branch " + branch;
		return Process.runButExitOnError( "hg", [ "commit", "-m", msg ], logger, [0,1] );
	}
	
	public static function branch( logger : ILog ) : String
	{
		var out = Process.runButExitOnError( "hg", [ "branch" ], logger );
		if( ! out.hasNext() )
		{
			Util.die( "Could not determine current branch." );
		}
		return out.next();
	}
	
	public static function revert( logger : ILog ) : Iter<String>
	{
		return Process.runButExitOnError( "hg", ["revert","-a"], logger );
	}

	//
	// Merge a specific revision into the current working directory by using
	// Mercurial's internal merging mechanism, which leaves conflict markers in
	// conflicting files.
	//
	public static function merge( revision : String, logger : ILog ) : Iter<String>
	{
		Sys.putEnv( "HGMERGE", "internal:merge" );
		return Process.runButExitOnError( "hg", ["merge", "-f", "-r", revision], logger );
	}

	public static function commit( message : String, logger : ILog ) : Iter<String>
	{
		return Process.runButExitOnError( "hg", ["commit", "-m", message], logger );
	}

	public static function update( revision : String, logger : ILog ) : Iter<String>
	{
		return Process.runButExitOnError( "hg", ["update", "-r", revision], logger );
	}

	public static function tag
	(
		tag : String,
		msg : String,
		logger : ILog
	)
	: Iter<String>
	{
		return Process.runButExitOnError( "hg", ["tag", tag, "-m", msg ], logger );
	}

	public static function getTags( logger : ILog ) : Iterator<String>
	{
		// Get the existing tags.
		return Process.runButExitOnError( "hg", ["-q", "tags"], logger );
	}

	public static function hasTag( tag : String, logger : ILog ) : Bool
	{
		for( t in getTags( logger ) )
		{
			if( t == tag ) return true;
		}
		return false;
	}
}
