// 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.

// This implementation was inspired by John Goerzen's hg-importdsc.

typedef Dsc =
{
	var dir : String;
	var changeLog : DebianChangeLog;
	var diff : String;
	var balls : Array<String>;
};

class MercurialImportDsc
{
	public static function main()
	{
		logger = new Script();
		var dscFileName = parseCommandLine();
		var dsc = parseDsc( dscFileName );
		Mercurial.exitIfUncommittedChanges( logger );
		// Check that the new version is not already imported.
		if( Mercurial.hasTag( Util.fullTag( dsc.changeLog ), logger ) )
		{
			Util.die
			(
				"Version " + dsc.changeLog.version +
				" already exists in repository."
			);
		}
		// Abort if new version is already incorporated.
		if( ! Util.tagOk( Mercurial.getTags( logger ), dsc.changeLog ) )
		{
			Util.die
			(
				"Version " + dsc.changeLog.version +
				" is not newer than existing Debian versions."
			);
		}
		try
		{
			// Is there a Debian diff? 
			if( dsc.diff.length > 0 ) processUpstream( dsc );
			// Import tarballs.
			importDsc( dscFileName );
			// Make sure debian/rules is executable.
			Process.runButExitOnError( "chmod", ["+x","debian/rules"], logger );
			// Add and remove files from dsc, but do a lot to detect renames.
			logger.info( 1, "Starting hg addremove -s " + Constants.similarity() );
			var addremove = new Process
			(
				"hg", [ "addremove", "-s", Constants.similarity() ]
			);
			if( addremove.process.exitCode() != 0 )
			{
				Util.writeError( "addremove failed:" );
				logger.infos( 0, addremove.stdouterr() );
				throw "Addremove dsc failed.";
			}
			logger.infos( 2, addremove.stdouterr() );
			// Commit the new dsc contents.
			logger.info( 2, "Starting hg commit" );
			var msg = Constants.importDsc() + " imported "
				+ dsc.changeLog.version;
			var commit = new Process( "hg", [ "commit", "-m", msg ] );
			if( commit.process.exitCode() != 0 )
			{
				Util.writeError( "commit failed:" );
				logger.infos( 0, commit.stdouterr() );
				throw "Commit dsc failed.";
			}
			logger.infos( 2, commit.stdouterr() );
		}		
		catch( e : Dynamic )
		{
			Util.writeError( Std.string( e ) );
			logger.info( 0, "Reverting default branch to state before import." );
			Mercurial.revert( logger );
			Sys.exit( 1 );
		}
		// Put a new tag in default branch.
		logger.info( 1, "Tagging with " + Util.fullTag( dsc.changeLog ) );
		var tag = Util.fullTag( dsc.changeLog );
		var msg = Constants.importDsc() + " added tag " + tag;
		var tagger = Mercurial.tag( tag, msg, logger );
		logger.infos( 2, tagger );
	}

	//
	// Setup global variables and abort if something is wrong with the command
	// line.  Returns the dsc file name.
	//
	static function parseCommandLine() : String
	{
		var usage = "Usage: " + Constants.importDsc() + " [-V|--version]" +
			" [-v|--verbose] dscFile";
		var options = new GetPot( Sys.args() );
		Util.maybeReportVersion( options, usage );
		// Collect verbosity options.
		while( options.got( ["--verbose","-v"] ) ) ++logger.verbosity;
		// Reject other options.
		Util.exitOnExtraneousOptions( options, usage );
		// Get the dsc file name.
		var dscFileName = options.unprocessed();
		if( dscFileName == null ) Util.die( usage );
		Util.exitOnExtraneousArguments( options, usage );
		// Check that the dsc file exists.
		if( ! FileUtil.fileExists( dscFileName ) )
		{
			Util.die( "File " + dscFileName + " does not exist." );
		}
		return dscFileName;
	}

	static function processUpstream( dsc : Dsc )
	{
		logger.info( 1, "Processing upstream tarball(s)." );
		var args : Array<String> = [ "--no-merge" ];
		for( ball in dsc.balls ) args.push( dsc.dir + "/" + ball );
		for( i in 0 ... logger.verbosity-1 ) args.push( "-v" );
		var output = Process.runButExitOnError( Constants.importOrig(), args, logger );
		// Filter out "to pull in new version" from importorig.
		for( line in output )
		{
			var pullInUpstream = ~/to pull in new version/;
			if( ! pullInUpstream.match( line ) ) logger.info( 1, line );
		}
	}

	static function parseDsc( dscFileName : String ) : Dsc
	{
		// Extract the source and version info.
		var dsc = sys.io.File.getContent( dscFileName );
		// Get the source from dsc contents.		
		var sourceRegExp = ~/^Source:[ \t]+(.+)$/m;
		if( ! sourceRegExp.match( dsc ) )
		{
			Util.die( "File " + dscFileName + " does not contain source info." );
		}
		var source = sourceRegExp.matched( 1 );
		// Get the version from dsc contents.
		var versionRegExp = ~/^Version:[ \t]+(.+)$/m;
		if( ! versionRegExp.match( dsc ) )
		{
			Util.die( "File " + dscFileName + " does not contain version info." );
		}
		var version = versionRegExp.matched( 1 );
		// Get the dpkg format.
		var formatRegExp = ~/^Format: (.*)$/m;
		if( ! formatRegExp.match( dsc ) )
		{
			Util.die ( "File " + dscFileName + " does not contain format info." );
		}
		var format = formatRegExp.matched( 1 );
		logger.info( 1, "Source format " + format );
		// Get the tarball file names.
		var balls = new Array<String>();
		var diff = "";
		if( format == "1.0" )
		{
			var origRegExp = ~/ ([^ ]+\.tar\.gz)$/m;
			if( origRegExp.match( dsc ) )
			{
				var ball = origRegExp.matched( 1 );
				logger.info( 1, "Tarball " + ball );
				balls.push( ball );
				var diffRegExp = ~/ ([^ ]+\.diff\.gz)$/m;
				if( diffRegExp.match( dsc ) )
				{
					diff = diffRegExp.matched( 1 );
					logger.info( 1, "Diff " + diff );
				}
			}
		}
		else if( format == "3.0 (native)" )
		{
			var origRegExp = ~/ ([^ ]+\.tar\.(gz|bz2|lzma|xz))$/m;
			if( origRegExp.match( dsc ) )
			{
				var ball = origRegExp.matched( 1 );
				balls.push( ball );
				logger.info( 1, "Tarball " + ball );
			}
		}
		else if( format == "3.0 (quilt)" )
		{
			// Find the debian diff.
			var diffRegExp = ~/ ([^ ]+\.debian\.tar\.(gz|bz2|lzma|xz))$/m;
			if( ! diffRegExp.match( dsc ) )
			{
				Util.die( "No debian diff in dsc file." );
			}
			diff = diffRegExp.matched( 1 );
			logger.info( 1, "Debian tarball " + diff );
			// Find the main tarball.
			var mainRegExp = ~/ ([^ ]+\.orig\.tar\.(gz|bz2|lzma|xz))$/m;
			if( mainRegExp.match( dsc ) )
			{
				var main = mainRegExp.matched( 1 );
				logger.info( 1, "Main tarball " + main );
				balls.push( main );
				// Collect additional tarballs.
				var searchable = dsc;
				while( searchable.length > 0 )
				{
					var componentRegExp =
					~/ ([^ ]+\.orig-[A_Za-z0-9][-A_Za-z0-9]*\.tar\.(gz|bz2|lzma|xz))$/m;
					if( componentRegExp.match( searchable ) )
					{
						var ball = componentRegExp.matched( 1 );
						if( ! Lambda.has( balls, ball ) )
						{
							logger.info( 1, "Additional tarball " + ball );
							balls.push( ball );
							searchable = componentRegExp.matchedRight();
						}
						else
						{
							// Stop the search.
							searchable = "";
						}
					}
					else
					{
						// Stop the search.
						searchable = "";
					}
				}
			}
		}
		if( balls.length == 0 )
		{
			// Search for 
			Util.die( "No source ball in dsc file." );
		}
		return
		{
			dir: haxe.io.Path.directory( dscFileName ),
			changeLog: new DebianChangeLog( source, version ),
			diff: diff,
			balls: balls
		};
	}

	static function importDsc( dscFileName : String )
	{
		// Delete everything except the Mercurial and Quilt files.
		for( entry in sys.FileSystem.readDirectory( "." ) )
		{			
			if( ! Constants.precious().match( entry ) ) FileUtil.unlink( entry );
		}
		// Make sure the dsc file has an absolute path.
		dscFileName = sys.FileSystem.fullPath( dscFileName );
		// Use dpkg-source to unpack everything in a temporary directory.  The
		// path cannot contain ".." because dpkg-source will complain about it.
		logger.info( 1, "Importing dsc." );
		var tmpDir =
			sys.FileSystem.fullPath( Sys.getCwd() + ".." ) +
			"/,,importdsc-" + Sys.time();
		var dpkgArgs = [ "--no-check", "--no-copy", "-x", dscFileName, tmpDir ];
		logger.info( 2, "Starting dpkg-source " + dpkgArgs.join(" ") + " in .." );
		logger.infos( 3, Process.runButExitOnError( "dpkg-source", dpkgArgs, logger, ".." ) );
		// Delete empty directories because they are not tracked by Mercurial.
		FileUtil.prune( tmpDir );
		// Move the unpacked source back into the root, unless it is a Mercurial
		// file.
		var entries = sys.FileSystem.readDirectory( tmpDir );
		for( entry in entries )
		{
			if( ! Constants.precious().match( entry ) )
			{
				logger.info( 3, "Copying " + tmpDir + "/" + entry );
				FileUtil.copy( tmpDir + "/" + entry, "." );
			}
		}
		logger.info( 2, "Deleting " + tmpDir );
		FileUtil.unlink( tmpDir );
	}

	static var logger : Script;
}

