/*
Copyright (c) 2003-2012, Frederico Caldeira Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
 */

importClass( java.io.BufferedWriter );
importClass( java.io.FileWriter );
importPackage( java.util.regex );
importPackage( com.yahoo.platform.yui.compressor );

CKRELEASER.load( 'ckreleaser.includes.io' );
CKRELEASER.load( 'ckreleaser.includes.skinprocessor' );
CKRELEASER.load( 'ckreleaser.includes.docsprocessor' );
CKRELEASER.load( 'ckreleaser.includes.tools' );

( function()
{
	CKRELEASER.releaser = function()
	{
		CKRELEASER.release = new release();
	};

	function release()
	{
		this.fixLineEnds = false;
		this.ignore = [];
		this.copy = [];
		this.rename = [];
		this.packages = [];
		this.documentation = {};
		this.skins = {};
		this.header = "";
	}

	release.prototype.isIgnoredPath = function( path, extraIgnoredPaths )
	{
		if ( extraIgnoredPaths && extraIgnoredPaths.length )
		{
			for ( var i = 0 ; i < extraIgnoredPaths.length ; i++ )
			{
				if ( path.replace( "\\", "/" ).endsWith( extraIgnoredPaths[i] ) )
					return true;
			}
		}

		if ( !this.ignore )
			return false;

		for ( var i = 0 ; i < this.ignore.length ; i++ )
		{
			if ( path.replace( "\\", "/" ).endsWith( this.ignore[i] ) )
				return true;
		}

		return false;
	};

	release.prototype.addIgnoredPath = function( path )
	{
		this.ignore.push( path );
	};

	release.prototype.removeIgnoredPath = function( path )
	{
		if ( !this.ignore )
			return;

		for ( var i = 0 ; i < this.ignore.length ; i++ )
		{
			if ( path == this.ignore[i] )
			{
				this.ignore.splice( i, 1 );
				return;
			}
		}
	};

	function renameFiles()
	{
		for ( var i = 0 ; i < CKRELEASER.release.rename.length ; i++ )
		{
			try
			{
				var sourceLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.rename[i].source );
				var targetLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.rename[i].target );
				sourceLocation.renameTo( targetLocation );
			}
			catch ( e )
			{
				print( "WARNING: Cannot rename " + CKRELEASER.release.rename[i].source + " into "
						+ CKRELEASER.release.rename[i].target + " " + e.toString() );
			}
		}
	}

	function copyFiles()
	{
		var sourceLocation, targetLocation;

		for ( var i = 0 ; i < CKRELEASER.release.copy.length ; i++ )
		{
			try
			{
				var ignoredFiles = [];
				if ( CKRELEASER.release.copy[i].ignore )
				{
					var packFile = new File( CKRELEASER.sourceDir, CKRELEASER.release.copy[i].ignore.sourcePackage );
					if ( !packFile.exists() )
						throw 'Package file ' + CKRELEASER.release.packages[i] + ' defined in ' + CKRELEASER.releaseFile
						  + ' not found (' + packFile.getCanonicalPath() + ')';
					
					var object = loadFile( packFile.getCanonicalPath() );
					ignoredFiles = eval('object.' + CKRELEASER.release.copy[i].ignore.files) || [];
				}

				sourceLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.copy[i].source );
				targetLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.copy[i].target );
				CKRELEASER.io.copy( sourceLocation, targetLocation, ignoredFiles );
			}
			catch ( e )
			{
				print( "WARNING: Cannot copy " + CKRELEASER.release.copy[i].source + " into " + CKRELEASER.release.copy[i].target
						+ " " + e.toString() );
			}
		}
	}

	function minifyFile( file )
	{
		var o =
		{
			output : ""
		};
		var ext = CKRELEASER.io.getExtension( file.getName() );

		if ( ext == "css" )
		{
			if ( CKRELEASER.verbose )
				print( 'Compressing ' + file );

			if ( CKRELEASER.isCompiled )
			{
				var isr = new InputStreamReader( new FileInputStream( file ), "UTF-8" );
				var compressor = new CssCompressor( isr );

				// Close the input stream first, and then open the output
				// stream,
				// in case the output file should override the input file.
				isr.close();

				var out = new OutputStreamWriter( new FileOutputStream( file ), "UTF-8" );
				compressor.compress( out, 4096 );
				out.close();
				
				// IE accept more than one filter if there is a space after a comma
				var content = CKRELEASER.io.readFile( file );
				content = content.replace( /\{filter:([^}]*),([^}]*?)\}/g, "{filter:$1, $2}"); 
				CKRELEASER.io.saveFile( file, content );
			}
			else
			{
				runCommand( "java", "-jar", "_dev/_thirdparty/yui/yuicompressor.jar", file.getAbsolutePath(), "--line-break",
						"4096", "-v", "--charset", "UTF-8", "-o", file.getAbsolutePath(), o );

				print( o.output );
			}
		}
		else if ( ext == "js" )
		{
			var fileName = file.getName();
			if ( fileName == 'ku.js' || fileName == 'fa.js' )
			{
				if ( CKRELEASER.verbose )
					print( '    INFO: File will not be minified: ' + file.getAbsolutePath() );
				return;
			}
			if ( CKRELEASER.verbose )
				print( 'Compressing ' + file );

			var source = CKRELEASER.io.readFile( file );
			try
			{
				var compressed = CKPACKAGER.scriptCompressor.compress( source, false, null, false, false );
				CKRELEASER.io.saveFile( file, compressed );
			}
			catch( e )
			{
				if ( CKRELEASER.verbose && typeof ( e.rhinoException ) != 'undefined' )
					e.rhinoException.printStackTrace();

				print( '\n    ERROR: compressing javascript file failed:\n    Error: ' + e.message + '\n    File: ' + file.getAbsolutePath()
						+ '\n    Line: ' + e.lineNumber );
			}
		}
	}

	function fixByteOrderMark( file )
	{
		if ( file.isDirectory() )
		{
			var children = file.list();
			for ( var i = 0 ; i < children.length ; i++ )
				fixByteOrderMark( new File( file, children[i] ) );
		}
		else
		{
			var extension = CKRELEASER.io.getExtension( file.getName() );
			switch ( extension )
			{
				case "gif" :
				case "jpg" :
				case "jpeg" :
				case "png" :
				case "gz" :
				case "zip" :
					break;

				case "asp" :
				case "js" :
					CKRELEASER.io.setByteOrderMark( file, true );
					break;

				default:
					CKRELEASER.io.setByteOrderMark( file, false );
					break;
			}
		}
	}

	function prependHeader( file )
	{
		CKRELEASER.io.saveFile( file, CKRELEASER.release.header + CKRELEASER.io.readFile( file ) );
	}

	function minifyFiles( file )
	{
		if ( file.isDirectory() )
		{
			var children = file.list();
			for ( var i = 0 ; i < children.length ; i++ )
				minifyFiles( new File( file, children[i] ) );
		}
		else
		{
			var ext = CKRELEASER.io.getExtension( file.getName() );

			switch ( ext )
			{
				case "css" :
				case "js" :
					minifyFile( file );
					prependHeader( file );
					break;
			}
		}
	}

	/**
	 * FIXME: this function shouldn't exist at all
	 */
	function cleanUp()
	{
		var file, text;

		file = new File( CKRELEASER.releaseDir, 'ckeditor.js' );
		if ( !file.exists() )
			throw "ckeditor.js not found";

		text = CKRELEASER.io.readFile( file );
		CKRELEASER.io.saveFile( file, text.replace( /_source\//g, '' ) );
	}

	var regexLib =
	{
		packagerRemove : Pattern.compile( '(?m-s:^.*?%REMOVE_START%).*?(?m-s:%REMOVE_END%.*?$)', Pattern.DOTALL ),
		packagerRemoveLine : Pattern.compile( '.*%REMOVE_LINE%.*(?:\\r\\n|\\r|\\n)?' ),
		packagerRev : Pattern.compile( '.*Last.*?Rev.*?: (\\d+).*', Pattern.DOTALL )
	};

	/*
	 * %VERSION%: 
	 *     the "version" string passed to the CKReleaser execution command.
	 * %REV%: 
	 *     the SVN revision number of the source directory.
	 * %TIMESTAMP%: 
	 *     a four characters string containing the
	 *     concatenation of the "Base 36" value of each of the following components
	 *     of the program execution date and time: year + month + day + hour.
	 * %REMOVE_LINE%: 
	 *     removes the line. 
	 * %REMOVE_START% and %REMOVE_END%: 
	 *     removes all lines starting from %REMOVE_START% to %REMOVE_END%, 
	 *     declaration line inclusive.
	 */
	function replaceDirectives( file )
	{
		var text = CKRELEASER.io.readFile( file );
		var replaced = false;

		if ( text.indexOf( "%VERSION%" ) != -1 )
		{
			text = text.replace( /%VERSION%/g, CKRELEASER.version );
			replaced = true;
		}
		if ( text.indexOf( "%REV%" ) != -1 )
		{
			text = text.replace( /%REV%/g, CKRELEASER.revisionNumber );

			replaced = true;
		}
		if ( text.indexOf( "%TIMESTAMP%" ) != -1 )
		{
			text = text.replace( /%TIMESTAMP%/g, CKRELEASER.timestamp );
			replaced = true;
		}
		if ( text.indexOf( "%REMOVE_START%" ) != -1 && text.indexOf( "%REMOVE_END%" ) != -1 )
		{
			text = String( regexLib.packagerRemove.matcher( text ).replaceAll( '%REMOVE_LINE%' ) );
			text = String( regexLib.packagerRemoveLine.matcher( text ).replaceAll( '' ) );
			replaced = true;
		}
		else if ( text.indexOf( "%REMOVE_LINE%" ) != -1 )
		{
			text = String( regexLib.packagerRemoveLine.matcher( text ).replaceAll( '' ) );
			replaced = true;
		}

		if ( replaced )
		{
			if ( CKRELEASER.verbose )
				print( "\r    Replacing directives in " + file );

			CKRELEASER.io.saveFile( file, text );
		}
	}

	function loadFile( filePath )
	{
		var file = new File( filePath );

		var releaseCode = 'var object = { ' + CKRELEASER.io.readFile( file ) + '\n};';
		var cx = Context.enter(), scope = cx.initStandardObjects();

		try
		{
			cx.evaluateString( scope, releaseCode, file.getName(), 1, null );
		}
		catch ( e )
		{
			if ( CKRELEASER.verbose && typeof ( e.rhinoException ) != 'undefined' )
				e.rhinoException.printStackTrace();

			error( '\nParsing release file failed:\n    Error: ' + e.message + '\n    File: ' + file.getAbsolutePath()
					+ '\n    Line: ' + e.lineNumber );
		}

		return scope.object;
	}

	function fixLineEndings( file )
	{
		if ( file.isDirectory() )
		{
			var children = file.list();
			for ( var i = 0 ; i < children.length ; i++ )
				fixLineEndings( new File( file, children[i] ) );
		}
		else
		{
			CKRELEASER.tools.fixLineEndings( file, true );
		}
	}

	CKRELEASER.releaser.prototype =
	{
		copyFiles : copyFiles,

		loadDefinitionFile : function( filePath )
		{
			this.loadDefinition( loadFile( filePath ) );
		},

		loadDefinition : function( definitionObject )
		{
			CKRELEASER.release = new release();

			if ( definitionObject.header )
				CKRELEASER.release.header = definitionObject.header;

			if ( definitionObject.fixLineEnds )
				CKRELEASER.release.fixLineEnds = definitionObject.fixLineEnds;

			if ( definitionObject.ignore )
				CKRELEASER.release.ignore = definitionObject.ignore;

			for ( var i = 0 ; i < CKRELEASER.release.ignore.length ; i++ )
				CKRELEASER.release.ignore[i] = CKRELEASER.release.ignore[i].replace( "\\", "/" );

			if ( definitionObject.copy )
				CKRELEASER.release.copy = definitionObject.copy;

			if ( definitionObject.rename )
				CKRELEASER.release.rename = definitionObject.rename;

			if ( definitionObject.packages )
				CKRELEASER.release.packages = definitionObject.packages;

			if ( definitionObject.documentation )
				CKRELEASER.release.documentation = definitionObject.documentation;

			if ( definitionObject.skins )
				CKRELEASER.release.skins = definitionObject.skins;

			if ( !CKRELEASER.release.packages.length )
				throw "Nothing to release: no packages found in the release file (" + CKRELEASER.releaseFile + ")";
		},

		processDirectives : function( file )
		{
			System.out.print( "." );
			if ( file.isDirectory() )
			{
				var children = file.list();
				for ( var i = 0 ; i < children.length ; i++ )
					this.processDirectives( new File( file, children[i] ) );
			}
			else if ( CKRELEASER.io.isTextFile( file.getName() ) )
				replaceDirectives( file );
		},

		run : function()
		{
			var error, i, info, time = new Date();

			var targetDir = new File( CKRELEASER.targetDir );
			var releaseDir = new File( CKRELEASER.releaseDir );
			var sourceDir = new File( CKRELEASER.sourceDir );

			if ( !sourceDir.exists() )
				throw 'Source directory does not exist (' + CKRELEASER.sourceDir + ')';
			if ( targetDir.exists() )
				throw 'Target directory already exists (' + CKRELEASER.targetDir + ')';

			if ( sourceDir.getCanonicalPath() == targetDir.getCanonicalPath() )
				throw "Source and target directory cannot be identical";

			if ( CKRELEASER.io.isChildPath( targetDir.getCanonicalPath(), sourceDir.getCanonicalPath() ) )
			{
				var _dir, targetDirIsIgnored = false;
				for ( var i = 0 ; i < CKRELEASER.release.ignore.length ; i++ )
				{
					_dir = new File( sourceDir, CKRELEASER.release.ignore[i] );
					if ( CKRELEASER.io.isChildPath( targetDir.getCanonicalPath(), _dir.getCanonicalPath() ) )
						targetDirIsIgnored = true;
				}
				if ( !targetDirIsIgnored )
					throw "Target directory must be located outside the source directory";
			}

			if ( !CKRELEASER.revisionNumber )
			{
				try
				{
					var o =
					{
						output : "",
						err : ""
					};

					runCommand( "svn", "info", CKRELEASER.sourceDir, o );

					// SVN may throw an error if path ends in ".."
					if ( o.err.indexOf( "which is unsupported" ) != -1 ) {
						runCommand( "svn", "info", sourceDir.getCanonicalPath(), o );
					}

					if ( o.output.indexOf( "Revision:" ) != -1 )
						CKRELEASER.revisionNumber = parseInt( regexLib.packagerRev.matcher( o.output ).replaceAll( '$1' ) );
					else
					{
						if ( o.output.indexOf( "not a working" ) != -1 )
							print( "\nWARNING: Unable to fetch the revision number, source directory is not under version control: "
									+ CKRELEASER.sourceDir );
						else
							print( "\nWARNING: Unable to fetch the revision number, make sure that Subversion's bin directory is in your system path." );
					}
				}
				catch( e )
				{
					print( "\nWARNING: Unable to run \"svn\" command to get the revision number, make sure that Subversion is installed and that Subversion's bin directory is in your system path." );
				}

				if ( !CKRELEASER.revisionNumber )
					CKRELEASER.revisionNumber = "UNKNOWN";
				print( CKRELEASER.revisionNumber );
			}

			if ( !CKRELEASER.timestamp )
			{
				var now = new Date();
				var timestamp = Integer.toString( now.getUTCFullYear() % 1000, 36 )
						+ Integer.toString( now.getUTCMonth(), 36 ) + Integer.toString( now.getUTCDate(), 36 )
						+ Integer.toString( now.getUTCHours(), 36 );

				if ( typeof CKRELEASER.revisionNumber == 'number' )
					timestamp += Integer.toString( CKRELEASER.revisionNumber, 36 );

				print( '\nGenerating timestamp (base36 encode of YYMMDDHH[revision_number]): ' + timestamp.toUpperCase() );

				CKRELEASER.timestamp = timestamp.toUpperCase();
			}

			if ( !releaseDir.mkdirs() )
				throw "Cannot create release directory (" + releaseDir + ")";

			print( "\nCopying files...\n" );
			CKRELEASER.io.copy( sourceDir, releaseDir );

			if ( CKRELEASER.runBeforeRelease )
			{
				print( "\nExecuting tasks before release...\n" );
				var o =
				{
					output : "",
					err : ""
				};
				runCommand( CKRELEASER.runBeforeRelease, o );
				print( o.output );

				if ( o.err ) {
					throw "Release process stopped, runBeforeRelease command returned errors:\n" + o.err;
				}
			}

			if ( CKRELEASER.release.copy )
			{
				print( "\nCopying files to special destination...\n" );
				copyFiles();
			}

			if ( CKRELEASER.release.rename )
			{
				print( "\nRenaming files...\n" );
				renameFiles();
			}

			print( "\nProcessing directives in files used by CKPackager...\n" );
			this.processDirectives( new File( CKPACKAGER.packDir ) );

			if ( CKRELEASER.release.packages )
			{
				print( "\nPackaging CKEditor...\n" );

				var packager, packFile;

				for ( i = 0 ; i < CKRELEASER.release.packages.length ; i++ )
				{
					packFile = new File( CKRELEASER.sourceDir, CKRELEASER.release.packages[i] );
					if ( !packFile.exists() )
						throw 'Package file ' + CKRELEASER.release.packages[i] + ' defined in ' + CKRELEASER.releaseFile
								+ ' not found (' + packFile.getCanonicalPath() + ')';

					packager = new CKPACKAGER.packager();
					packager.loadDefinitionFile( packFile.getCanonicalPath() );
					packager.run();
				}
			}
			else
				print( "\nWARNING: no package definitions found in the release file.\n" );

			if ( CKRELEASER.release.skins.source )
			{
				print( "\nCreating skins...\n" );
				var skinProcessor = new CKRELEASER.skinProcessor();
				skinProcessor.createSkins( CKRELEASER.sourceDir, CKRELEASER.releaseDir );

				if ( CKRELEASER.release.skins.minify )
					minifyFiles( new File( CKRELEASER.releaseDir, CKRELEASER.release.skins.target ) );
			}
			else
				print( "\nWARNING: skins directive not found in the release file.\n" );

			if ( CKRELEASER.release.documentation.target )
			{
				print( "\nCreating documentation...\n" );
				var docsProcessor = new CKRELEASER.docsProcessor();
				docsProcessor.createDocumentation( CKRELEASER.sourceDir, CKRELEASER.releaseDir );

				var docsDir = new File( CKRELEASER.releaseDir, CKRELEASER.release.documentation.target );
				info = CKRELEASER.io.getDirectoryInfo( docsDir );
				print( "    Number of created files: " + info.files );
				print( "    Total size.............: " + info.size + " bytes" );
			}
			else
				print( "\nWARNING: documentation directive not found in the release file.\n" );

			if ( CKRELEASER.release.copy )
			{
				print( "\nMinifying files...\n" );
				for ( i = 0 ; i < CKRELEASER.release.copy.length ; i++ )
				{
					if ( CKRELEASER.release.copy[i].minify )
					{
						var targetLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.copy[i].target );
						minifyFiles( targetLocation );
					}
				}
			}

			/* FIXME: directives are processed twice in some directories */
			print( "\nProcessing directives in files...\n" );
			this.processDirectives( releaseDir );

			print( "\n\nCleaning up...\n" );
			cleanUp();

			print( "\nFixing line endings...\n" );
			fixLineEndings( releaseDir );

			print( "\nFixing byte-order mark in files...\n" );
			fixByteOrderMark( new File( CKRELEASER.releaseDir ) );

			// get information about release directory
			info = CKRELEASER.io.getDirectoryInfo( releaseDir );

			print( "\nCreating compressed files...\n" );

			var zipFile = new File( CKRELEASER.targetDir, CKRELEASER.fileName + ".zip" );
			CKRELEASER.io.zipDirectory( CKRELEASER.releaseDir, zipFile, "ckeditor" );
			print( "    Created " + zipFile.getName() + "...: " + zipFile.length() + " bytes ("
					+ Math.round( zipFile.length() / info.size * 100 ) + "% of original)" );

			var tarFile = new File( CKRELEASER.targetDir, CKRELEASER.fileName + ".tar.gz" );
			CKRELEASER.io.targzDirectory( CKRELEASER.releaseDir, tarFile, "ckeditor" );
			print( "    Created " + tarFile.getName() + ": " + tarFile.length() + " bytes ("
					+ Math.round( tarFile.length() / info.size * 100 ) + "% of original)" );

			print( "\n==========================" );

			time = ( new Date() ) - time;
			time = time / 1000;

			print( "Release process completed:\n" );
			print( "    Number of files: " + info.files );
			print( "    Total size.....: " + info.size + " bytes" );
			print( "    Time taken.....: " + time + " seconds" );
			print( "" );

			if ( error )
			{
				print( "Error details:" );
				print( error );
				print( "" );
			}
		}
	};
} )();
