/*
 * Project Sc2gears
 * 
 * Copyright (c) 2010 Andras Belicza <[email protected]>
 * 
 * This software is the property of Andras Belicza.
 * Copying, modifying, distributing, refactoring without the authors permission
 * is prohibited and protected by Law.
 */
package hu.belicza.andras.sc2gearsdb.common.client;

import hu.belicza.andras.sc2gearsdb.common.client.beans.DownloadParamsProvider;

import java.util.Date;
import java.util.Map.Entry;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasKeyPressHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.http.client.URL;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;

/**
 * General utilities for the client sides of GWT modules.
 * 
 * @author Andras Belicza
 */
public class ClientUtils {
	
	public static final DateTimeFormat DATE_TIME_FORMAT      = DateTimeFormat.getFormat( "yyyy-MM-dd HH:mm:ss" );
	public static final DateTimeFormat HTML_TIMESTAMP_FORMAT = DateTimeFormat.getFormat( "'<b>'yyyy-MM-dd'</b>' HH:mm:ss" );
	public static final NumberFormat   NUMBER_FORMAT         = NumberFormat  .getFormat( "#,###" );
	public static final DateTimeFormat DATE_FORMAT           = DateTimeFormat.getFormat( "yyyy-MM-dd" );
	
	/**
	 * Adds a style name to a widget and returns the widget
	 * @param widget    widget to add the style name to
	 * @param styleName style name to be added
	 * @param <T> type of the widget
	 * @return the widget
	 */
	public static < T extends Widget > T styledWidget( final T widget, final String styleName ) {
		widget.addStyleName( styleName );
		return widget;
	}
	
	/**
	 * Creates an empty widget with a fixed vertical height.
	 * @param height height of the widget to create
	 * @return an empty widget with a fixed vertical height
	 */
	public static Widget createVerticalEmptyWidget( final int height ) {
		final Label l = new Label();
		l.setHeight( height + "px" );
		return l;
	}
	
	/**
	 * Creates an empty widget with a fixed horizontal width.
	 * @param width width of the widget to create
	 * @return an empty widget with a fixed horizontal width
	 */
	public static Widget createHorizontalEmptyWidget( final int width ) {
		final Label l = new Label();
		l.setWidth( width + "px" );
		return l;
	}
	
	/**
	 * Creates a file download button.
	 * @param downloadParamsProvider download parameters provider
	 * @param fileType               type of file to create a download button for
	 * @param sha1                   SHA-1 of the file
	 * @param fileName               name of the file
	 * @param sharedAccount          Google account to access in case we're viewing a shared account; <code>null</code> otherwise
	 * @return the created file download button
	 */
	public static Button createFileDownloadButton( final DownloadParamsProvider downloadParamsProvider, final String fileType, final String sha1, final String fileName, final String sharedAccount ) {
		final Button downloadButton = new ImageButton( "drive-download.png", "Download \"" + fileName + "\"" );
		
		downloadButton.addClickHandler( new ClickHandler() {
			@Override
			public void onClick( final ClickEvent event ) {
				final FormPanel formPanel = new FormPanel();
				formPanel.setAction( "/file/" + URL.encode( fileName ).replace( "#", "%23" ) ); // Browsers break file name from the end of the URL if '#' is found...
				formPanel.setMethod( FormPanel.METHOD_POST );
				
				final VerticalPanel formContentPanel = new VerticalPanel();
				Hidden hidden;
				for ( final Entry< String, String > entry : downloadParamsProvider.getDownloadParameterMap().entrySet() ) {
					hidden = new Hidden( entry.getKey(), entry.getValue() );
					formContentPanel.add( hidden );
				}
				hidden = new Hidden( downloadParamsProvider.getFileTypeParamName(), fileType ); formContentPanel.add( hidden );
				hidden = new Hidden( downloadParamsProvider.getSha1ParamName    (), sha1     ); formContentPanel.add( hidden );
				if ( sharedAccount != null ) {
					hidden = new Hidden( downloadParamsProvider.getSharedAccountParamName(), sharedAccount ); formContentPanel.add( hidden );
				}
				formPanel.setWidget( formContentPanel );
				
				// We have to add it to the root panel, else download will not work in FireFox and in Internet Explorer
				RootPanel.get().add( formPanel );
				formPanel.addSubmitCompleteHandler( new SubmitCompleteHandler() {
					@Override
					public void onSubmitComplete( final SubmitCompleteEvent event ) {
						RootPanel.get().remove( formPanel );
					}
				} );
				// Have to submit "deferred", else download will not work in FireFox (form is submitted, but response is not processed => file not saved)
				Scheduler.get().scheduleDeferred( new ScheduledCommand() {
					@Override
					public void execute() {
						formPanel.submit();
					}
				} );
			}
		} );
		
		return downloadButton;
	}
	
	/**
	 * Initiates a batch file download.
	 * @param downloadParamsProvider download parameters provider
	 * @param fileType               type of files to download
	 * @param sha1List               a comma separated list of SHA-1 of the files to download
	 * @param sharedAccount          Google account to access in case we're viewing a shared account; <code>null</code> otherwise
	 */
	public static void initiateBatchDownload( final DownloadParamsProvider downloadParamsProvider, final String fileType, final String sha1List, final String sharedAccount ) {
		final FormPanel formPanel = new FormPanel();
		
		formPanel.setAction( "/file/" + URL.encode( fileType + "_pack.zip" ) );
		formPanel.setMethod( FormPanel.METHOD_POST );
		
		final VerticalPanel formContentPanel = new VerticalPanel();
		Hidden hidden;
		for ( final Entry< String, String > entry : downloadParamsProvider.getBatchDownloadParameterMap().entrySet() ) {
			hidden = new Hidden( entry.getKey(), entry.getValue() );
			formContentPanel.add( hidden );
		}
		hidden = new Hidden( downloadParamsProvider.getFileTypeParamName(), fileType ); formContentPanel.add( hidden );
		hidden = new Hidden( downloadParamsProvider.getSha1ListParamName(), sha1List ); formContentPanel.add( hidden );
		if ( sharedAccount != null ) {
			hidden = new Hidden( downloadParamsProvider.getSharedAccountParamName(), sharedAccount ); formContentPanel.add( hidden );
		}
		formPanel.setWidget( formContentPanel );
		
		// We have to add it to the root panel, else download will not work in FireFox and in Internet Explorer
		RootPanel.get().add( formPanel );
		formPanel.addSubmitCompleteHandler( new SubmitCompleteHandler() {
			@Override
			public void onSubmitComplete(SubmitCompleteEvent event) {
				RootPanel.get().remove( formPanel );
			}
		} );
		
		// Have to submit "deferred", else download will not work in FireFox (form is submitted, but response is not processed => file not saved)
		Scheduler.get().scheduleDeferred( new ScheduledCommand() {
			@Override
			public void execute() {
				formPanel.submit();
			}
		} );
	}
	
	/**
	 * Formats the specified amount of seconds to a short format.
	 * @param seconds seconds to be formatted
	 * @return the formatted seconds in short format
	 */
	public static String formatSeconds( int seconds ) {
		int hour = 0, min = 0;
		
		if ( seconds >= 3600 ) {
			hour = seconds / 3600;
			seconds  = seconds % 3600;
		}
		if ( seconds >= 60 ) {
			min = seconds / 60;
			seconds = seconds % 60;
		}
		
		return hour > 0 ? hour + ( min < 10 ? ":0" : ":" ) + min + ( seconds < 10 ? ":0" : ":" ) + seconds
				: min + ( seconds < 10 ? ":0" : ":" ) + seconds;
	}
	
	/**
	 * Displays a Details dialog box.<br>
	 * Renders a table with 2 columns: name and value pairs.
	 * @param caption title of the dialog
	 * @param values  values to be displayed; each element is an array (name-value pair) which defines a row
	 */
	public static void displayDetailsDialog( final String caption, final Object[][] values ) {
		final DialogBox dialogBox = new DialogBox( true );
		dialogBox.setText( caption );
		dialogBox.setGlassEnabled( true );
		
		final VerticalPanel content = new VerticalPanel();
		content.setHorizontalAlignment( HasHorizontalAlignment.ALIGN_CENTER );
		
		final FlexTable table = new FlexTable();
		table.setBorderWidth( 1 );
		table.setCellSpacing( 0 );
		table.setCellPadding( 3 );
		
		
		final CellFormatter cellFormatter = table.getCellFormatter();
		for ( int i = 0; i < values.length; i++ ) {
			// Name
			table.setWidget( i, 0, new Label( values[ i ][ 0 ].toString() ) );
			cellFormatter.addStyleName( i, 0, "headerRow" );
			
			final Object value = values[ i ] [ 1 ];
			
			if ( value == null )
				table.setWidget( i, 1, new Label() );
			else if ( value instanceof Widget ) 
				table.setWidget( i, 1, (Widget) value );
			else if ( value instanceof Date )
				table.setWidget( i, 1, createTimestampWidget( (Date) value ) );
			else {
				String stringValue;
				if ( value instanceof String )
					stringValue = (String) value;
				else if ( value instanceof Number )
					stringValue = NUMBER_FORMAT   .format( (Number) value );
				else
					stringValue = value.toString();
				table.setWidget( i, 1, new Label( stringValue ) );
			}
			cellFormatter.addStyleName( i, 1, "row" + ( i & 0x01 ) );
			cellFormatter.setHorizontalAlignment( i, 1, HasHorizontalAlignment.ALIGN_LEFT );
		}
		
		content.add( table );
		
		content.add( createVerticalEmptyWidget( 8 ) );
		content.add( ClientUtils.createDialogCloseButton( dialogBox, "Close" ) );
		content.add( createVerticalEmptyWidget( 8 ) );
		
		dialogBox.setWidget( content );
		
		dialogBox.center();
	}
	
	/**
	 * Sets common style for the specified button to be <i>small<i>.
	 * @param button button to be styled
	 */
	public static void styleSmallButton( final Button button ) {
		DOM.setStyleAttribute( button.getElement(), "fontSize", "85%" );
	}
	
	/**
	 * Sets the font size style attribute of the specified widget.
	 * @param widget   widget whose font size to be set
	 * @param fontSize font size to be set, for example <code>"85%"</code>
	 * @param <T> type of the widget
	 * @return the widget
	 */
	public static < T extends Widget > T setWidgetFontSize( final T widget, final String fontSize ) {
		DOM.setStyleAttribute( widget.getElement(), "fontSize", fontSize );
		return widget;
	}
	
	/**
	 * Sets the horizontal alignment of all cells of the specified table.
	 * @param table      table whose cells to be aligned
	 * @param hAlignment horizontal alignment to be set
	 */
	public static void alignTableCells( final HTMLTable table, final HorizontalAlignmentConstant hAlignment ) {
		final CellFormatter cellFormatter = table.getCellFormatter();
		
		for ( int i = table.getRowCount() - 1; i >= 0; i-- )
			for ( int j = table.getCellCount( i ) - 1; j >= 0; j-- )
				cellFormatter.setHorizontalAlignment( i, j, hAlignment );
	}
	
	/**
	 * Adds a {@link KeyPressHandler} to the specified widget which calls {@link Button#click()} on <code>targetButton</code>
	 * when the Enter key is pressed.
	 * @param widget       widget to add the key handler to
	 * @param targetButton target button to activate when the enter key is pressed
	 */
	public static void addEnterTarget( final HasKeyPressHandlers widget, final Button targetButton ) {
		widget.addKeyPressHandler( new KeyPressHandler() {
			@Override
			public void onKeyPress( final KeyPressEvent event ) {
				if ( event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER )
					targetButton.click();
			}
		} );
	}
	
	/**
	 * Creates and returns a {@link Button} which when activated closes the specified dialog box.
	 * @param dialogBox  dialog box to close when the close button is activated
	 * @param buttonText text of the close button
	 * @return a button which when activated closes the specified dialog box
	 */
	public static Button createDialogCloseButton( final DialogBox dialogBox, final String buttonText ) {
		return new Button( buttonText, new ClickHandler() {
			@Override
			public void onClick( final ClickEvent event ) {
				dialogBox.hide();
			}
		} );
	}
	
	/**
	 * Returns a widget which displays a formatted time stamp.
	 * @param date time stamp to be displayed
	 * @return a widget which displays a formatted time stamp
	 */
	public static Widget createTimestampWidget( final Date date ) {
		return new HTML( HTML_TIMESTAMP_FORMAT.format( date ) );
	}
	
	/**
	 * Returns a widget which displays a formatted time stamp.
	 * @param text text to prepend to the formatted time stamp
	 * @param date time stamp to be displayed
	 * @return a widget which displays a formatted time stamp
	 */
	public static Widget createTimestampWidget( final String text, final Date date ) {
		return new HTML( text + HTML_TIMESTAMP_FORMAT.format( date ) );
	}
	
	/**
	 * Returns a widget which indicates that the logged in user is being checked.
	 * @return a widget which indicates that the logged in user is being checked
	 */
	public static Widget createCheckingUserWidget() {
		final HorizontalPanel panel = new HorizontalPanel();
		panel.setHeight( "20px" );
		
		panel.add( new Image( "/images/loading.gif" ) );
		panel.add( createHorizontalEmptyWidget( 3 ) );
		panel.add( styledWidget( new Label( "Checking user..." ), "note" ) );
		
		return panel;
	}
	
	/**
	 * Creates and adds a logout link to the Root panel, at the top right absolute position.
	 * @param text      text of the logout link
	 * @param logoutUrl logout URL
	 * @return the logout link
	 */
	public static Anchor createAndSetupLogoutLink( final String text, final String logoutUrl ) {
		final Anchor logoutLink = new Anchor( text, logoutUrl );
		
		// Absolute position to the top right
		DOM.setStyleAttribute( logoutLink.getElement(), "position", "absolute" );
		DOM.setStyleAttribute( logoutLink.getElement(), "top"     , "2px"      );
		DOM.setStyleAttribute( logoutLink.getElement(), "right"   , "4px"      );
		
		RootPanel.get().add( logoutLink );
		
		return logoutLink;
	}
	
	/**
	 * Creates a new {@link Anchor} and adds a {@link ClickHandler} to it.
	 * @param text    text of the anchor
	 * @param handler click handler to be added
	 * @return the created anchor
	 */
	public static Anchor createAnchorWithHandler( final String text, final ClickHandler handler ) {
		final Anchor a = new Anchor( text );
		a.addClickHandler( handler );
		return a;
	}
	
	/**
	 * Sets an icon to the specified widget.<br>
	 * The widget will be set as a background image on the left side.
	 * @param widget    widget to set the icon for
	 * @param imageName name of the icon image file, relative to the <code>/images/</code> folder
	 * @param width     optional icon width; 18 pixel is used if not provided
	 * @param color     optional background color to be set for the widget; use <code>"transparent"</code> for no background
	 */
	public static void setWidgetIcon( final Widget widget, final String imageName, final Integer width, final String color ) {
		DOM.setStyleAttribute( widget.getElement(), "background", ( color == null ? "" : color ) + " url('/images/" + imageName + "') no-repeat 1px center" );
		DOM.setStyleAttribute( widget.getElement(), "paddingLeft", width == null ? "18px" : width + "px" );
	}
	
	/**
	 * Creates a new window and returns a reference to it.
	 * @param url      url to be passed to it
	 * @param name     name to be passed to it
	 * @param features features to be passed to it
	 * @return a reference to the new window
	 */
	public static native JavaScriptObject createNewWindow( final String url, final String name, final String features ) /*-{
		return $wnd.open( url, name, features );
	}-*/;
	
	/**
	 * Sets the location of a window
	 * @param window   window whose location to be set
	 * @param location location to be set 
	 */
	public static native void setWindowLocation( final JavaScriptObject window, final String location ) /*-{
		window.location = location;
	}-*/;
	
	/**
	 * Closes a window.
	 * @param window window to be closed
	 */
	public static native void closeWindow( final JavaScriptObject window ) /*-{
		window.close();
	}-*/;
	
	/**
	 * Tracks a Google Analytics page view.<br>
	 * Google Analytics tracking code must be included in the host html.<br>
	 * Asynchronous implementation. Synchronous would be:
	 * <pre>$wnd.pageTracker._trackPageview(pageName);</pre>
	 * @param pageName page name to be reported
	 */
	public static native void trackAnalyticsPageView( final String pageName ) /*-{
		$wnd._gaq.push(['_trackPageview', pageName]);
	}-*/;
	
}