// Copyright (c) 2009, 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 MercurialPristineTar
{
	public static function main()
	{
		logger = new Script();
		var usage = "Usage: " + Constants.pristineTar() + " [-V|--version]"
		+ " [-v|--verbose] [version]"
		;
		var options = new GetPot( Sys.args() );
		Util.maybeReportVersion( options, usage );
		// Collect verbosity options.
		while( options.got( ["--verbose","-v"] ) ) ++logger.verbosity;
		// We cannot get to other branches if there are pending changes.
		Mercurial.exitIfUncommittedChanges( logger );
		// Extract package information from the changelog file.
		Mercurial.switchToBranch( Constants.mainBranch(), logger );
		var changeLog = DebianChangeLog.parseCurrentChangeLog( logger );
		if( changeLog.version.debianRevision == null )
		{
			Util.die( "Cannot generate pristine tarball for a native package." );
		}
		var fileNameBase = changeLog.source + "_";
		// Use a specified version or the most recent from the changelog.
		Util.exitOnExtraneousOptions( options, usage );
		var version = options.unprocessed();
		if( version == null )
		{
			version = changeLog.version.upstream;
		}
		Util.exitOnExtraneousArguments( options, usage );
		fileNameBase += version;		
		// Find the matching tarball xdeltas, and map them to the component
		// name.
		var deltas = new Map<String, Pristine>();
		Mercurial.switchToBranch( Constants.pristineBranch(), logger );
		var entries = sys.FileSystem.readDirectory( "." );
		for( entry in entries )
		{
			var versionRegExp = new EReg
			(
				"^(" + RegExpUtil.escape( fileNameBase ) + "\\.orig" +
				"(-([A-Za-z0-9][-A-Za-z0-9]*))?" + "\\.tar\\.)([a-z0-9]+)\\." +
				Constants.pristineExtension() + "$",
				""
			);
			if( versionRegExp.match( entry ) )
			{
				// Read the branch revision.
				var revision = sys.io.File.getContent
				(
					versionRegExp.matched( 1 ) + Constants.revisionExtension()
				);
				// We may want the root dir of the source tree in the tarball
				var tld = changeLog.source + "-" + version;
				if (sys.FileSystem.exists (versionRegExp.matched( 1 ) + Constants.dirExtension()))
					tld = sys.io.File.getContent (versionRegExp.matched( 1 ) + Constants.dirExtension()); // FIXME: escape & and % (at least)
				var escape = new EReg ("[%&]", "");
				var pristineInfo = new Pristine
				(
					versionRegExp.matched( 4 ),
					entry,
					versionRegExp.matched( 1 ) + Constants.xdeltaExtension(),
					revision,
					escape.replace (tld, "\\$1")
				);
				var component = versionRegExp.matched( 3 );
				// Main tarball wil have a null component.
				if( component == null ) component = ".";
				deltas.set( component, pristineInfo );
				logger.info( 3, component + " => " + pristineInfo );
			}
		}
		if( Lambda.count( deltas ) == 0 )
		{
			die( "No xdeltas found for version " + version );
		}
		// Extract each component from its dedicated branch and recreate the
		// pristine tarball from the right revision by using the xdelta in the
		// pristine branch.
		for( component in deltas.keys() )
		{
			var pristineInfo = deltas.get( component );
			var tarball = pristineInfo.tarballDelta.substr
			(
				0, pristineInfo.tarballDelta.lastIndexOf(".")
			);
			logger.info( 2, "Switching to revision " + pristineInfo.revision );
			logger.infos( 3, Mercurial.update( pristineInfo.revision, logger ) );
			// Recreate a tarball from the component branch contents by
			// transforming the file names so they are rooted.
			var recreated = "recreated-" + tarball;
			var tarBaseArgs = ["--mode=0", "--owner=0", "--group=0"];
			var tarArgs = tarBaseArgs.concat (["-cof", recreated]);

			if( component == "." )
			{
				tarArgs = tarArgs.concat
				([
					"--no-recursion",
					"--transform",
					"s%^.%" + pristineInfo.directory + "%"
				]);
				tarArgs.push( "." );

				logger.info( 3, "Starting " + tarArgs.join(" ") );
				var pack = new Process( "tar", tarArgs );
				if( pack.process.exitCode() != 0 )
				{
					logger.infos( 0, pack.stdouterr() );
					die( "Could not recreate " + tarball );
				}

				// Now for the rest of it...
				tarArgs = tarBaseArgs.concat ([
							"-rof", recreated,
							"--transform",
							"s%^.%" + pristineInfo.directory + "/&%"
						  ]);
			}
			for( entry in sys.FileSystem.readDirectory( "." ) )
			{
				if( ! Constants.precious().match( entry ) )
				{
					tarArgs.push( entry );
				}
			}
			logger.info( 3, "Starting " + tarArgs.join(" ") );
			var pack = new Process( "tar", tarArgs );
			if( pack.process.exitCode() != 0 )
			{
				logger.infos( 0, pack.stdouterr() );
				die( "Could not recreate " + tarball );
			}
			logger.infos( 3, pack.stdouterr() );
			// tarball.
			Mercurial.switchToBranch( Constants.pristineBranch(), logger );
			var args =
			[
				"patch", "-p", pristineInfo.tarballDelta, recreated, tarball
			];
			logger.info( 2, "Starting xdelta " + args.join(" ") );
			var xdelta = Process.runButExitOnError( "xdelta", args, logger );
			logger.info( 2, "Created " + tarball );
			// Use one of the pristine info to recreate the pristine compressed
			// tarball.
			var pristine = Util.pristineCompress
			(
				tarball,
				pristineInfo.compressionDelta,
				pristineInfo.compression
			);
			if( ! pristine )
			{
				die( "Could not recreate pristine tarball for " + component );
			}
			var compressedTarball = tarball + "." + pristineInfo.compression;
			sys.io.File.copy( compressedTarball, "../" + compressedTarball );
			logger.info( 0, "Created ../" + compressedTarball );
			// Cleanup.
			FileUtil.unlink( tarball );
			FileUtil.unlink( recreated );
			FileUtil.unlink( compressedTarball );
			FileUtil.unlink( "bin" );
		}
		Mercurial.switchToBranch( Constants.mainBranch(), logger );
		Sys.exit( 0 );
	}

	//
	// Exit with an error message but swith back to main branch first.
	//
	static function die( msg : String )
	{
		Mercurial.switchToBranch( Constants.mainBranch(), logger );
		Util.die( msg );
	}

	static var logger : Script;
}
