
import se.datadosen.jalbum.*;
import se.datadosen.util.Dates;
import se.datadosen.util.IO;

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.text.MessageFormat;
import java.util.*;
import javax.swing.*;

public class Index extends Common
{
	public Index( AlbumBean engine )
	{
		super( engine );
	}

	public String getCurrentIndexFullTitle()
	{
		Map vars = getVariableContainer( imageDirectory );

		if( !vars.containsKey( Constants.CACHE_KEY_INDEX_HTML_TITLE ) )
			vars.put( Constants.CACHE_KEY_INDEX_HTML_TITLE, getCurrentIndexFullTitleImpl() );
		return ( String ) vars.get( Constants.CACHE_KEY_INDEX_HTML_TITLE );
	}

	private String getCurrentIndexFullTitleImpl()
	{
		String indexHtmlTitle = readUserVariableAsString(Constants.USER_VAR_INDEX_HTML_TITLE_CONTENT, null, Constants.DEFAULT_INDEX_HTML_TITLE);
		StringTokenizer st = new StringTokenizer(indexHtmlTitle, ",;|~");
		String result = "";

		while( st.hasMoreTokens() )
		{
			String token = st.nextToken().trim();

			if( token.equalsIgnoreCase("albumtitle") )
				result = GeneralUtils.appendString( result, getAlbumTitle(), " - ");
			else if( token.equalsIgnoreCase( "title" ) )
				result = GeneralUtils.appendString( result, getDirectoryTitle(imageDirectory), " - " );
			else if( token.equalsIgnoreCase( "filename" ) )
				result = GeneralUtils.appendString( result, imageDirectory.getName(), " - " );
			else if( token.equalsIgnoreCase( "date" ) )
				result = GeneralUtils.appendString( result, getDirectoryDate(imageDirectory), " - " );
			else if( token.equalsIgnoreCase( "slidecount" ) )
				result = GeneralUtils.appendString( result, "(" + getDirectorySlideCount(imageDirectory) + " " + texts.getString( "slides" ) + ")", " - " );
		}

		return GeneralUtils.clearHtmlCode(result);
	}

	public String getIndexTopPathBar()
	{
		Map vars = getVariableContainer( imageDirectory );

		if( !vars.containsKey( Constants.CACHE_KEY_INDEX_TOP_PATH_BAR ) )
			vars.put( Constants.CACHE_KEY_INDEX_TOP_PATH_BAR, getIndexTopPathBarImpl( imageDirectory, "" ) );
		return ( String ) vars.get( Constants.CACHE_KEY_INDEX_TOP_PATH_BAR );
	}

	private String getIndexTopPathBarImpl( File dir, String prefix )
	{
		StringBuffer buffer = new StringBuffer();
		String title = getDirectoryTitle( dir );
		int max_length = getMaximumFolderLengthOnTopPathBar();

		if( !isTopLevelFolder( dir ) )
		{
			buffer.append( getIndexTopPathBarImpl( dir.getParentFile(), prefix + "../" ) );
			buffer.append( Constants.SKIN_TOP_BAR_PATH_SEPARATOR );
		}

		buffer.append( "<a class=\"path\" href=\"" );
		if( prefix.length()>0 )
		{
			buffer.append( prefix );
			buffer.append( engine.getIndexPageName() );
			buffer.append( engine.getPageExtension() );
		}
		else
			buffer.append( firstIndexPage );
		buffer.append( "\"" );

		if( max_length>0 && title.length()>max_length )
		{
			buffer.append( " title=\"" );
			buffer.append( GeneralUtils.clearHtmlCode( title ) );
			buffer.append( "\"" );
			title = title.substring( 0, max_length ) + Constants.SKIN_TRUNCATED_TEXT_POSTFIX;
		}
		buffer.append( ">" );
		buffer.append( title );
		buffer.append( "</a>" );
		return buffer.toString();
	}

	private String getSlideVoiceAnnotationPath( File myfile )
	{
		File sound = getOriginalVoiceAnnotationFile( myfile );

		if( sound==null )
			return null;

		return engine.getCloseupDirectory() + "/" + sound.getName();
	}

	public String getFolderVoiceAnnotationConsole()
	{
		String voicePath = getFolderVoiceAnnotationPath( imageDirectory );

		if( voicePath==null )
			return null;

		if( isVariableValueEqual( Constants.USER_VAR_SHOW_AUDIO_CONSOLE, imageDirectory, true ) )
		{
			Dimension audioConsoleSize = getAudioConsoleSize();
			Object[] args = new Object[] {voicePath, new Integer(audioConsoleSize.width), new Integer(audioConsoleSize.height)};

			return MessageFormat.format(MediaUtils.getAudioBlockTemplate(), args);
		}
		else
			return MessageFormat.format(MediaUtils.getAudioHiddenBlockTemplate(), new Object[] {voicePath});
	}

	private String getFolderVoiceAnnotationPath( File dir )
	{
		File sound = getOriginalVoiceAnnotationFile( dir );

		if( sound==null )
			return null;

		String soundPath;

		if( !outputDirectory.equals( imageDirectory ) )
		{
			if( imageDirectory.equals( dir ) )
			{
				GeneralUtils.copyFile( sound, outputDirectory );
				soundPath = sound.getName();
			}
			else
				soundPath = dir.getName() + "/" + sound.getName();
		}
		else
			soundPath = se.datadosen.util.IO.relativePath( sound, outputDirectory );

		return soundPath;
	}

	public String getFolderBackgroundPath()
	{
		Map vars = getVariableContainer( imageDirectory );
		String key = Constants.CACHE_KEY_PREFIX + Constants.META_INDEX_BACKGROUND_IMAGE;

		if( !vars.containsKey( key ) )
			vars.put( key, getFolderBackgroundPathImpl() );

		return ( String ) vars.get( key );
	}

	private String getFolderBackgroundPathImpl()
	{
		File background = getBackgroundImageFile( Constants.META_INDEX_BACKGROUND_IMAGE, imageDirectory );

		if( background==null )
			return null;

		String backgroundPath;

		if( !outputDirectory.equals( imageDirectory ) || !isInsideAlbum( background ) )
		{
			GeneralUtils.copyFile( background, outputDirectory );
			backgroundPath = background.getName();
		}
		else
			backgroundPath = se.datadosen.util.IO.relativePath( background, new File( outputDirectory.getPath() ) );

		return backgroundPath;
	}

	private String getDirectoryInfoImpl( File dir, String info_key, String default_value, boolean tooltip )
	{
		String info_content = readUserVariableAsString( info_key, dir.getParentFile(), default_value );
		StringTokenizer st = new StringTokenizer( info_content, ",;|~" );
		String result = "";

		while( st.hasMoreTokens() )
		{
			String token = st.nextToken().trim();

			if( token.equalsIgnoreCase( "title" ) )
				result = GeneralUtils.appendString( result, getDirectoryTitle( dir ), tooltip );
			else if( token.equalsIgnoreCase( "filename" ) )
				result = GeneralUtils.appendString( result, dir.getName(), tooltip );
			else if( token.equalsIgnoreCase( "date" ) )
				result = GeneralUtils.appendString( result, getDirectoryDate( dir ), tooltip );
			else if( token.equalsIgnoreCase( "header" ) )
				result = GeneralUtils.appendString( result, getHeaderContent( dir, true ), tooltip );
			else if( token.equalsIgnoreCase( "footer" ) )
				result = GeneralUtils.appendString( result, getFooterContent( dir, true ), tooltip );
			else if( token.equalsIgnoreCase( "slidecount" ) )
				result = GeneralUtils.appendString( result, "(" + getDirectorySlideCount( dir ) + " " + texts.getString( "slides" ) + ")", tooltip );
			else if( token.equalsIgnoreCase("remarkscount") )
			{
				if( shouldIncludeViewerRemarksOnIndex() )
					result = GeneralUtils.appendString(result, getViewerRemarksCount(dir) + " " + texts.getString( "remarks-section" ), tooltip);
				else
					log( "[WARN] You've included 'RemarksCount' as part of thumbnails' information but 'Viewer Remarks' feature is OFF!" );
			}
		}

		return ( tooltip ? GeneralUtils.clearHtmlCode( result ) : result );
	}

	private String getDirectoryDate( File dir )
	{
		return Dates.format( Dates.toDate( dir.lastModified() ), engine.getDateFormat() );
	}

	private String getDirectoryTooltip( File dir )
	{
		Map vars = getVariableContainer( dir );

		if( !vars.containsKey( Constants.CACHE_KEY_THUMBNAIL_TOOLTIP ) )
			vars.put( Constants.CACHE_KEY_THUMBNAIL_TOOLTIP, getDirectoryInfoImpl( dir, Constants.USER_VAR_FOLDER_TOOLTIP_CONTENT, Constants.DEFAULT_FOLDER_TOOLTIP_CONTENT, true ) );

		return ( String ) vars.get( Constants.CACHE_KEY_THUMBNAIL_TOOLTIP );
	}

	private String getDirectoryCaption( File dir )
	{
		return getDirectoryInfoImpl( dir, Constants.USER_VAR_FOLDER_CAPTION_CONTENT, Constants.DEFAULT_FOLDER_CAPTION_CONTENT, false );
	}

	private String getThumbnailCaption( File myfile )
	{
		return getThumbnailInfoImpl( myfile, Constants.USER_VAR_THUMBNAIL_CAPTION_CONTENT, Constants.DEFAULT_THUMBNAIL_CAPTION_CONTENT, false );
	}

	public String getHeaderContent()
	{
		return getHeaderContent( null, false );
	}

	private String getHeaderContent( File dir, boolean tooltip )
	{
		Map vars = getVariableContainer( dir );
		String cacheKey = Constants.CACHE_KEY_PREFIX + Constants.META_FOLDER_HEADER + tooltip;

		if( !vars.containsKey( cacheKey ) )
			vars.put( cacheKey, getHeaderContentImpl( dir, tooltip ) );

		return ( String ) vars.get( cacheKey );
	}

	private String getHeaderContentImpl( File dir, boolean tooltip )
	{
		String headerContent = readFromFolderMetadata( Constants.META_FOLDER_HEADER, dir );

		if( isEmptyString(headerContent ) )
		{
			File header_file = new File( ( dir!=null ) ? dir : imageDirectory, "header.inc" );

			if( header_file.exists() )
				headerContent = GeneralUtils.readTextFile( header_file );
		}

		return ( tooltip ? GeneralUtils.clearHtmlCode( headerContent ) : headerContent );
	}

	private File getFolderIcon(File dir)
	{
		File folderIcon;

		if( !getAddMiniFolderSign(dir) )
			folderIcon = new File( resDirectory, Constants.SKIN_ACTION_RESOURCE_DIR + getIconSet( dir.getParentFile() ) + "/folder.gif" );
		else
		{
			Map vars = (Map)fileVariables.get(dir);

			folderIcon = (File) vars.get("representingFile");
			if( !folderIcon.exists() )
			{
				log("The specified folder icon '" + folderIcon + "' for '" + dir + "' directory does not exist!");
				displayMessage( "The specified folder icon '" + folderIcon + "' does not exist!" );
			}
		}

		return folderIcon;
	}

	private boolean getAddMiniFolderSign(File dir)
	{
		Map vars = (Map)fileVariables.get(dir);
		String thumbPath = (String) vars.get("thumbPath");

		return( !thumbPath.endsWith(engine.getResourceDirectory() + "\\folder.gif") && !thumbPath.endsWith(engine.getResourceDirectory() + "/folder.gif") );
	}

	// Generate drop box options for quick navigation
	public String getJumpingMenuItemsContent()
	{
		String template = getJumpingMenuItems();
		Object[] args = new Object[totalIndexes];

		for( int i = 0; i<totalIndexes; i++ )
		{
			if( i==indexNum - 1 )
				args[i] = "selected=\"selected\"";
			else
				args[i] = "";
		}
		return GeneralUtils.formatBigTemplate( template, args );
	}

	private String getJumpingMenuItems()
	{
		Map vars = getVariableContainer( imageDirectory );

		if( !vars.containsKey( Constants.CACHE_KEY_INDEX_JUMP_TO_PAGE ) )
			vars.put( Constants.CACHE_KEY_INDEX_JUMP_TO_PAGE, getJumpingMenuItemsImpl() );

		return ( String ) vars.get( Constants.CACHE_KEY_INDEX_JUMP_TO_PAGE );
	}

	private String getJumpingMenuItemsImpl()
	{
		// Make links to sibling directories
		StringBuffer buffer = new StringBuffer();
		File parentDir = imageDirectory.getParentFile();
		String template = "<option value=\"{0}\" {1}>{2}</option>\n";
		int max_length = getMaximumFolderLengthOnTopPathBar();
		Object[] args = new Object[3];
		String title;

		if( isInsideAlbum( parentDir ) )
		{
			File[] files = listFiles( parentDir, true, programDirectory );

			for( int i = 0; i<files.length; i++ )
			{
				if( !files[i].isDirectory() )
					continue;

				title = getDirectoryTitle( files[i] );
				args[0] = "../" + encodeUrlIfNecessary( files[i].getName() ) + "/" + engine.getIndexPageName() + engine.getPageExtension();
				if( files[i].equals( imageDirectory ) )
				{
					args[1] = ( totalIndexes==1 ) ? "selected=\"selected\"" : "";
					args[2] = "- ";
				}
				else
				{
					args[1] = "";
					args[2] = "+ ";
				}

				if( max_length>0 && title.length()>max_length )
					title = title.substring( 0, max_length ) + Constants.SKIN_TRUNCATED_TEXT_POSTFIX;
				args[2] = args[2] + title;

				buffer.append( MessageFormat.format( template, args ) );
				if( files[i].equals( imageDirectory ) && totalIndexes>1 )
					writeIndexCurrentFolderPages( buffer, Constants.SKIN_JUMPING_MENU_HIERARCHY_SEPARATOR + "- " );
			}
		}
		else
			writeIndexCurrentFolderPages( buffer, "- " );

		return buffer.toString();
	}

	private void writeIndexCurrentFolderPages( StringBuffer buffer, String prefix )
	{
		String template = "<option value=\"{0}\" {1}>" + prefix + texts.getString( "page" ) + " {2}</option>\n";
		Object[] args = new Object[3];

		for( int i = 1; i<=totalIndexes; i++ )
		{
			args[0] = engine.getIndexPageName() + ( i==1 ? "" : "" + i ) + engine.getPageExtension();
			args[1] = "{" + ( i - 1 ) + "}";
			args[2] = "" + i;

			buffer.append( MessageFormat.format( template, args ) );
		}
	}

	public int getDirectorySlideCount( File dir )
	{
		Map vars = getVariableContainer( dir );

		if( !vars.containsKey( Constants.CACHE_KEY_DIRECTORY_SLIDE_COUNT ) )
		{
			int count = 0;

			try
			{
				count = engine.countFiles( dir );
			}
			catch( IOException e )
			{
				log( e );
			}
			vars.put( Constants.CACHE_KEY_DIRECTORY_SLIDE_COUNT, new Integer( count ) );
		}

		return ( ( Integer ) vars.get( Constants.CACHE_KEY_DIRECTORY_SLIDE_COUNT ) ).intValue();
	}

	private AlbumImage buildAlbumImage( File file )
	{
		try
		{
			return new AlbumImage(file, engine);
		}
		catch( Exception e )
		{
			// TODO: This is just a workaround to the NPE problem when "AlbumImage(file, engine)" constructor
			// gets into an unknown file extension!

			try
			{
				//ImageIcon imageIcon = new ImageIcon( URLEncoder.encode( file.toURL().toString() ) );
				ImageIcon imageIcon = new ImageIcon( file.toURL() );
				Image image = imageIcon.getImage();
				BufferedImage buffImage;

				if( imageIcon.getImageLoadStatus()==MediaTracker.ABORTED || imageIcon.getImageLoadStatus()==MediaTracker.ERRORED )
				{
					log( "The specified icon '" + file + "' is an invalid image!" );
					displayMessage( "The specified icon '" + file + "' is an invalid image!" );
					buffImage = new BufferedImage( 100, 100, BufferedImage.TYPE_INT_RGB );
				}
				else
					buffImage = new BufferedImage( image.getWidth( null ), image.getHeight( null ), BufferedImage.TYPE_INT_RGB );

				Graphics g = buffImage.getGraphics();

				g.drawImage(image, 0, 0, null);

				// By Igor Lubashev: A fix to OUT-OF-MEMORY problem! Seems the JVM does not free the memory used by this object.
				image.flush();

				return new AlbumImage(buffImage, engine);
			}
			catch( IOException ex )
			{
				log( ex );
				return null;
			}
		}
	}

	public String[] writeFolderThumbnail( File dir, PrintWriter out )
	{
		Map vars = ( Map ) fileVariables.get( dir );
		String tooltip = getDirectoryTooltip( dir );
		String linkPath = ( String ) vars.get( "closeupPath" );
		File folderThumbnail = getFolderIcon( dir );
		Dimension folderThumbnailDimension;
		String frameBlock;

		if( getAddMiniFolderSign( dir ) )
		{
			LogoFilter logoFilter = ( LogoFilter ) album.get( Constants.CACHE_KEY_LOGO_FILTER );
			BPPFrameHandler thumbFrame = ( BPPFrameHandler ) album.get( Constants.CACHE_KEY_FOLDER_FRAME_FILTER );
			File logoFile = new File( resDirectory, Constants.SKIN_ACTION_RESOURCE_DIR + getIconSet( dir.getParentFile() ) + "/folder_small.gif" );
			String folderThumbnailPath = dir.getName() + "/" + getBPPGeneratedImageDirectory() + folderThumbnail.getName();

			try
			{
				AlbumImage albumImage = applyStandardFiltersAsThumbnail(buildAlbumImage( folderThumbnail ), vars);

				logoFilter.setSrc( logoFile.toURL().toString() );
				albumImage = albumImage.applyFilter(logoFilter, vars);
				albumImage = albumImage.applyFilter(thumbFrame, vars);
				folderThumbnailPath = GeneralUtils.replaceFileExtension(folderThumbnailPath, Constants.SKIN_FOLDER_THUMBNAIL_POSTFIX);
				folderThumbnail = new File( outputDirectory, folderThumbnailPath );
				albumImage.saveJPEG( GeneralUtils.ensureParentFoldersExist( folderThumbnail ), engine.getQualityPercent() );
				albumImage.getImage().flush();
			}
			catch( IOException e )
			{
				log( e );
			}

			folderThumbnailDimension = GeneralUtils.getImageDimension( folderThumbnail, maxThumbWidth, maxThumbHeight );
			frameBlock = getFrameBlock( encodeUrlIfNecessary( folderThumbnailPath ), linkPath, false, tooltip, folderThumbnailDimension, true );
		}
		else
		{
			folderThumbnailDimension = GeneralUtils.getImageDimension( folderThumbnail, maxThumbWidth, maxThumbHeight );

			String imageExtraParameters = ( "width=\"" + folderThumbnailDimension.width + "\" height=\"" + folderThumbnailDimension.height + "\"" );
			String folderThumbnailPath = IO.relativePath( folderThumbnail, outputDirectory );

			frameBlock = getActionBlock( linkPath, null, tooltip, encodeUrlIfNecessary( folderThumbnailPath ), imageExtraParameters );
		}

		out.println( frameBlock );

		String[] props = new String[4];

		props[0] = linkPath;
		props[1] = truncateCaptionIfNeccessary( getDirectoryCaption( dir ) );
		props[2] = getFolderVoiceAnnotationPath( dir );
		props[3] = "TYPE_FOLDER";
		return props;
	}

	private AlbumImage applyStandardFiltersAsThumbnail(AlbumImage albumImage, Map vars)
	{
		Iterator it;

		it = engine.filterIterator(JAFilter.THUMBNAILS_PRESCALE_STAGE);
		while( it.hasNext() )
		{
			JAFilter filter = (JAFilter) it.next();

			if( !(filter instanceof BPPFrameHandler) )
				albumImage = albumImage.applyFilter(filter, vars);
		}
		albumImage = albumImage.scaleToThumbnail();
		it = engine.filterIterator(JAFilter.THUMBNAILS_POSTSCALE_STAGE);
		while( it.hasNext() )
		{
			JAFilter filter = (JAFilter) it.next();

			if( !(filter instanceof BPPFrameHandler) )
				albumImage = albumImage.applyFilter(filter, vars);
		}
		return albumImage;
	}

	public String[] writeSlideThumbnail( File myfile, PrintWriter out )
	{
		Map vars = ( Map ) fileVariables.get( myfile );
		Dimension displaySize = new Dimension( maxThumbWidth, maxThumbHeight );
		String tooltip = getThumbnailTooltip( myfile );
		String linkPath;
		String displayPath;
		boolean apply_size_on_display = true;

		if( isMovieFile( myfile ) )
		{
			linkPath = engine.getSlideDirectory() + "/" + vars.get( "label" ) + engine.getPageExtension();

			BPPFrameHandler thumbFrame = ( BPPFrameHandler ) album.get( Constants.CACHE_KEY_THUMBNAIL_FRAME_FILTER );
			File thumbnail = getMovieThumbnailFile( myfile );
			String iconSet = getIconSet( myfile );

			if( thumbnail==null )
			{
				displayPath = getBPPGeneratedImageDirectory() + Constants.SKIN_MOVIE_STANDARD_GENERATED_THUMBNAIL;
				apply_size_on_display = false;

				try
				{
					AlbumImage albumImage = buildAlbumImage( new File( resDirectory, Constants.SKIN_ACTION_RESOURCE_DIR + iconSet + "/movie.gif" ) );

					albumImage = albumImage.applyFilter( thumbFrame, vars );
					albumImage.saveJPEG( GeneralUtils.ensureParentFoldersExist( new File( outputDirectory, displayPath ) ), engine.getQualityPercent() );
					albumImage.getImage().flush();
				}
				catch( IOException e )
				{
					log( e );
				}
			}
			else
			{
				displayPath = GeneralUtils.replaceFileExtension(getBPPGeneratedImageDirectory() + thumbnail.getName(), Constants.SKIN_MOVIE_THUMBNAIL_POSTFIX );
				try
				{
					LogoFilter logoFilter = ( LogoFilter ) album.get( Constants.CACHE_KEY_LOGO_FILTER );
					AlbumImage albumImage = applyStandardFiltersAsThumbnail( buildAlbumImage( thumbnail ), vars );
					File outputThumbnail = new File( outputDirectory, displayPath );

					logoFilter.setSrc( new File( resDirectory, Constants.SKIN_ACTION_RESOURCE_DIR + iconSet + "/movie_small.gif" ).toURL().toString() );
					albumImage = albumImage.applyFilter( logoFilter, vars );
					albumImage = albumImage.applyFilter( thumbFrame, vars );
					albumImage.saveJPEG( GeneralUtils.ensureParentFoldersExist( outputThumbnail ), engine.getQualityPercent() );
					albumImage.getImage().flush();
					displaySize = GeneralUtils.getImageDimension( outputThumbnail, maxThumbWidth, maxThumbHeight );
				}
				catch( IOException e )
				{
					log( e );
				}
			}

			fixMovieLocation( myfile );
		}
		else
		{
			linkPath = ( String ) vars.get( "closeupPath" );
			displayPath = ( String ) vars.get( "thumbPath" );
			displaySize.width = ( ( Integer ) vars.get( "thumbWidth" ) ).intValue();
			displaySize.height = ( ( Integer ) vars.get( "thumbHeight" ) ).intValue();
		}

		String frame_block = getFrameBlock( displayPath, linkPath, false, tooltip, displaySize, apply_size_on_display );

		out.println( frame_block );

		String[] props = new String[4];

		props[0] = linkPath;
		props[1] = truncateCaptionIfNeccessary( getThumbnailCaption( myfile ) );
		props[2] = getSlideVoiceAnnotationPath( myfile );
		props[3] = "TYPE_SLIDE";
		return props;
	}

	private void fixMovieLocation( File myfile )
	{
		// JAlbum itself doesn't do a good job on copying the movies because of the following
		// reasons so doing it manually:
		//   - Copying the movies into the root of the destination folder (instead of "slides")
		//     which causes problems when playing them with WMP (it doesn't support playing from
		//     any other folder than the current because of the security reasons).
		//   - It does not copy the movies when "Copy Originals" is not checked (but they're
		//     always needed regardless of status of this option).
		File destDir = new File( outputDirectory.getPath(), engine.getCloseupDirectory() + "/" );

		destDir.mkdirs();
		GeneralUtils.copyFile( myfile, destDir );
	}

	public String getCurrentIndexNavigationBar()
	{
		String iconSetPath = getIconSetPath( imageDirectory );
		StringBuffer buffer = new StringBuffer();

		if( totalIndexes>1 )
		{
			if( !isEmptyString( previousIndexPage ) )
			{
				if( isVariableValueEqual( Constants.USER_VAR_INCLUDE_FIRST_LAST_PAGE_ACTIONS, imageDirectory, true ) )
				{
					buffer.append( getActionBlock( firstIndexPage, texts.getString( "first-page" ), iconSetPath + "/first.gif" ) );
					buffer.append( '\n' );
				}
				buffer.append( getActionBlock( previousIndexPage, texts.getString( "prev-page" ), iconSetPath + "/previous.gif" ) );
				buffer.append( '\n' );
			}
			else
			{
				if( isVariableValueEqual( Constants.USER_VAR_INCLUDE_FIRST_LAST_PAGE_ACTIONS, imageDirectory, true ) )
				{
					buffer.append( getActionBlock( null, texts.getString( "on-first-page" ), iconSetPath + "/first_disabled.gif" ) );
					buffer.append( '\n' );
				}
				buffer.append( getActionBlock( null, texts.getString( "on-first-page" ), iconSetPath + "/previous_disabled.gif" ) );
				buffer.append( '\n' );
			}

			buffer.append( Constants.SKIN_NAVIGATION_BAR_SEPARATOR );
			buffer.append( '\n' );
		}

		if( !isEmptyString( parentIndexPage ) )
		{
			buffer.append( getActionBlock( parentIndexPage, texts.getString( "up" ), iconSetPath + "/up.gif" ) );
			buffer.append( '\n' );
		}
		else
		{
			String externalHome = getExternalHomePath(false);

			if( !isEmptyString(externalHome) )
			{
				buffer.append( getActionBlock( externalHome, texts.getString( "home" ), iconSetPath + "/up.gif" ) );
				buffer.append( '\n' );
			}
		}

		if( totalIndexes>1 )
		{
			buffer.append( Constants.SKIN_NAVIGATION_BAR_SEPARATOR );
			buffer.append( '\n' );

			if( !isEmptyString( nextIndexPage ) )
			{
				buffer.append( getActionBlock( nextIndexPage, texts.getString( "next-page" ), iconSetPath + "/next.gif" ) );
				buffer.append( '\n' );

				if( isVariableValueEqual( Constants.USER_VAR_INCLUDE_FIRST_LAST_PAGE_ACTIONS, imageDirectory, true ) )
				{
					buffer.append( getActionBlock( lastIndexPage, texts.getString( "last-page" ), iconSetPath + "/last.gif" ) );
					buffer.append( '\n' );
				}
			}
			else
			{
				buffer.append( getActionBlock( null, texts.getString( "on-last-page" ), iconSetPath + "/next_disabled.gif" ) );
				buffer.append( '\n' );

				if( isVariableValueEqual( Constants.USER_VAR_INCLUDE_FIRST_LAST_PAGE_ACTIONS, imageDirectory, true ) )
				{
					buffer.append( getActionBlock( null, texts.getString( "on-last-page" ), iconSetPath + "/last_disabled.gif" ) );
					buffer.append( '\n' );
				}
			}
		}

		return buffer.toString();
	}

	private int getMaximumThumbnailCaptionLength()
	{
		if( isVariableValueEqual( Constants.USER_VAR_ENABLE_TEXT_TRUNCATION, imageDirectory, false ) )
			return -1;

		return readUserVariableAsInteger( Constants.USER_VAR_MAXIMUM_THUMBNAIL_CAPTION_LENGTH, imageDirectory, -1 );
	}

	private String truncateCaptionIfNeccessary( String caption )
	{
		int max_length = getMaximumThumbnailCaptionLength();

		if( max_length>0 && caption.length()>max_length )
			caption = caption.substring( 0, max_length ) + Constants.SKIN_TRUNCATED_TEXT_POSTFIX;
		return caption;
	}

	public String getCurrentIndexBreadCrumbTrail()
	{
		Map vars = getVariableContainer( imageDirectory );

		if( !vars.containsKey( Constants.CACHE_KEY_INDEX_BREADCRUMB_TRAIL ) )
			vars.put( Constants.CACHE_KEY_INDEX_BREADCRUMB_TRAIL, getCurrentIndexBreadCrumbTrailImpl() );
		return ( String ) vars.get( Constants.CACHE_KEY_INDEX_BREADCRUMB_TRAIL );
	}

	private String getCurrentIndexBreadCrumbTrailImpl()
	{
		String breadcrumbTrail = readUserVariableAsString(Constants.USER_VAR_INDEX_BREADCRUMB_TRAIL_CONTENT, null, Constants.DEFAULT_INDEX_BREADCRUMB_TRAIL);
		StringTokenizer st = new StringTokenizer(breadcrumbTrail, ",;|~");
		String result = "";

		while( st.hasMoreTokens() )
		{
			String token = st.nextToken().trim();

			if( token.equalsIgnoreCase( "date" ) )
				result = GeneralUtils.appendString( result, getDirectoryDate( imageDirectory ), " - " );
			else if( token.equalsIgnoreCase( "slidecount" ) )
				result = GeneralUtils.appendString( result, "(" + getDirectorySlideCount( imageDirectory ) + " " + texts.getString( "slides" ) + ")", " - " );
		}

		return result;
	}

	public String getCurrentIndexViewerRemarksId()
	{
		return getViewerRemarksId( imageDirectory );
	}
}
