// 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 Util
{
	public static function writeOutput( line : String )
	{
		var stdout = Sys.stdout();
		stdout.writeString( line );
		stdout.writeString( "\n" );
		stdout.flush();
	}
	
	public static function writeInfo( line : String )
	{
		writeOutput( "I: " + line );
	}
	
	public static function writeWarning( line : String )
	{
		writeOutput( "W: " + line );
	}
	
	public static function writeError( line : String )
	{
		var stderr = Sys.stderr();
		stderr.writeString( "E: " + line );
		stderr.writeString( "\n" );
		stderr.flush();
	}
	
	public static function writeOutputs( lines : Iterable<String> )
	{
		Lambda.iter( lines, writeOutput );
	}
	
	public static function writeErrors( lines : Iterable<String> )
	{
		Lambda.iter( lines, writeError );
	}

	public static function writeInfos( lines : Iterable<String> )
	{
		Lambda.iter( lines, writeInfo );
	}

	public static function die( error : String )
	{
		writeError( error );
		Sys.exit( 1 );
	}

	public static function dies( errors : Iterable<String> )
	{
		writeErrors( errors );
		Sys.exit( 1 );
	}

	public static function maybeReportVersion( options : GetPot, usage : String )
	{
		if( options.got( ["--version","-V"] ) )
		{
			// There cannot be other arguments than --version.
			Util.exitOnExtraneousArguments( options, usage );
			// Output the version number.
			Util.writeOutput( Constants.version() );
			Sys.exit( 0 );
		}
		if( options.got( ["--help", "-h", "-?"] ) )
		{
			// There cannot be other arguments than --help.
			Util.exitOnExtraneousArguments( options, usage );
			Util.writeOutput( usage );
			Sys.exit( 0 );
		}
	}
	
	public static function fullTag( changeLog : DebianChangeLog ) : String
	{
		var versionWithoutColons =
			StringTools.replace( changeLog.version.toString(), ":", ";" );
		return changeLog.source + "_" + versionWithoutColons;
	}

	public static function upstreamTag
	(
		changeLog : DebianChangeLog,
		component : String
	)
	: String
	{
		var versionWithoutColons =
			StringTools.replace( changeLog.version.upstream, ":", ";" );
		if( component == "." )
		{
			return changeLog.source + "_" + versionWithoutColons;
		}
		else
		{
			return changeLog.source + "-" + component + "_"
				+ versionWithoutColons;
		}
	}

	public static function componentBranch( source : String, component : String )
	: String
	{
		if( component == "." ) return source;
		else return source + "-" + component;
	}

	// Returns false if there exist tags greater than the specified version.
	public static function tagOk
	(
		tags : Iterator<String>,
		pkg : DebianChangeLog
	)
	: Bool
	{
		var next = pkg.version.toString();
		for( tag in tags )
		{
			var changeLog = parseTag( tag );
			if( changeLog != null && changeLog.source == pkg.source )
			{
				// Compare the two versions and return false if an existing
				// version is newer than the next version.
				var existing = changeLog.version.toString();
				var compare = new Process
				(
					"dpkg",
					["--compare-versions", existing, "ge", next ]
				);
				if( compare.process.exitCode() == 0 ) return false;
			}
		}
		// All existing versions are older.
		return true;
	}

	public static function parseTag( tag : String ) : Null<DebianChangeLog>
	{
		var parts = tag.split( "_" );
		if( parts.length != 2 ) return null;
		return new DebianChangeLog( parts[ 0 ], parts[ 1 ] );
	}

	public static function exitOnExtraneousOptions
	(
		options : GetPot,
		usage : String
	)
	{
		var unknown = options.unknown();
		if( unknown != null )
		{
			while( unknown != null )
			{
				Util.writeError( "Extraneous option " + unknown );
				unknown = options.unknown();
			}
			die( usage );
		}
	}

	public static function exitOnExtraneousArguments
	(
		options : GetPot,
		usage : String
	)
	{
		exitOnExtraneousOptions( options, usage );
		var unprocessed = options.unprocessed();
		if( unprocessed != null )
		{
			while( unprocessed != null )
			{
				Util.writeError( "Extraneous argument " + unprocessed );
				unprocessed = options.unprocessed();
			}
			die( usage );
		}
	}

	//
	// Uncompress a compressed file.  Returns false on error.
	//
	public static function uncompress( source : String, target : String )
	: Bool
	{
		var unpack : Process = null;
		var gz = ~/\.gz$/;
		var bz = ~/\.bz2$/;
		var lzma = ~/\.lzma$/;
		var xz = ~/\.xz$/;
		if( gz.match( source ) )
		{
			unpack = new Process
			(
				"sh", ["-c","gzip -d -c "+source+" > "+target]
			);
		}
		else if( bz.match( source ) )
		{
			unpack = new Process
			(
				"sh", ["-c","bzip2 -d -c "+source+" > "+target]
			);
		}
		else if( lzma.match( source ) )
		{
			unpack = new Process
			(
				"sh", ["-c","lzma -d -c "+source+" > "+target]
			);
		}
		else if( xz.match( source ) )
		{
			unpack = new Process
			(
				"sh", ["-c","xz -d -c "+source+" > "+target]
			);
		}
		else
		{
			writeError( "Do not know how to uncompress " + source + "." );
			return false;
		}
		// Check the unpacking process.
		if( unpack.process.exitCode() != 0 )
		{
			// Process failed, abort.
			writeError( "Uncompressing " + source + " failed:" );
			writeErrors( unpack.stdouterr() );
			return false;
		}
		return true;
	}

	//
	// Create enough information to be able to compress a file so that it
	// becomes identical an original compressed file.  Returns false on error.
	//
	public static function preparePristine( source : String, target : String )
	: Bool
	{
		var pristine : Process = null;
		var gz = ~/\.gz$/;
		var bz = ~/\.bz2$/;
		var lzma = ~/\.lzma$/;
		var xz = ~/\.xz$/;
		if( gz.match( source ) )
		{
			pristine = new Process
			(
				"pristine-gz", ["gendelta",source,target]
			);
		}
		else if( bz.match( source ) )
		{
			pristine = new Process
			(
				"pristine-bz2", ["gendelta",source,target]
			);
		}
		else if( lzma.match( source ) )
		{
			writeWarning( "Cannot guarantee recreation of lzma files." );
			writeWarning( "See pristine-tar manual." );
			pristine = new Process( "touch", [target] );
		}
		else if( xz.match( source ) )
		{
			pristine = new Process
			(
				"pristine-xz", ["gendelta",source,target]
			);
		}
		else
		{
			writeError( "Do not know how to handle " + source );
			return false;
		}
		// Check the unpacking process.
		if( pristine.process.exitCode() != 0 )
		{
			// Process failed, abort.
			writeError( "pristine-{gz,bz2,xz} " + source + " failed:" );
			writeErrors( pristine.stdouterr() );
			return false;
		}
		return true;
	}

	//
	// Recreate a compressed file using previously created information and the
	// uncompressed version.  Returns false on error.
	//
	public static function pristineCompress
	(
		source : String,
		delta : String,
		extension : String
	)
	: Bool
	{
		var pristine : Process = null;
		switch( extension )
		{
			case "gz":
				pristine = new Process
				(
					"pristine-gz", ["gengz",delta,source]
				);
			case "bz2":
				pristine = new Process
				(
					"pristine-bz2", ["genbz2",delta,source]
				);
			case "lzma":
				writeWarning( "Cannot guarantee pristine lzma files." );
				pristine = new Process
				(
					"sh", ["-c","lzma -c "+source+" > "+source+".lzma"]
				);
			case "xz":
				writeWarning( "Cannot guarantee pristine xz files." );
				pristine = new Process
				(
					"pristine-xz", ["genxz",delta,source]
				);
			default:
				writeError( "Do not know how to handle " + extension );
				return false;
		}
		// Check the unpacking process.
		if( pristine.process.exitCode() != 0 )
		{
			// Process failed, abort.
			writeError( "pristine-{gz,bz2,xz} " + source + " failed:" );
			writeErrors( pristine.stdouterr() );
			return false;
		}
		return true;
	}

	//
	// Extract a tarball into a directory (which must exist).  Returns false on
	// error.
	//
	public static function extract( tarball : String, directory : String )
	{
		var unpack : Process = null;
		var tgz = ~/\.tar\.gz$/;
		var tbz = ~/\.tar\.bz2$/;
		var tlzma = ~/\.tar\.lzma$/;
		var txz = ~/\.tar\.xz$/;
		if( tgz.match( tarball ) )
		{
			unpack = new Process( "tar", ["xCfz",directory,tarball] );
		}
		else if( tbz.match( tarball ) )
		{
			unpack = new Process( "tar", ["xCfj",directory,tarball] );
		}
		else if( tlzma.match( tarball ) )
		{
			unpack = new Process
			(
				"tar", ["xC",directory,"--lzma","-f",tarball]
			);
		}
		else if( txz.match( tarball ) )
		{
			unpack = new Process
			(
				"tar", ["xC",directory,"--xz","-f",tarball]
			);
		}
		else
		{
			writeError( "Do not know how to unpack " + tarball + "." );
			return false;
		}
		// Check the unpacking process.
		if( unpack.process.exitCode() != 0 )
		{
			// Process failed, abort.
			writeError( "Unpacking " + tarball + " failed:" );
			writeErrors( unpack.stdouterr() );
			return false;
		}
		return true;
	}
}
	
