/*******************************************************************************
 * Copyright (c) 2004, 2010 Jeff Pound and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Jeff Pound - initial API and implementation
 *     Tasktop Technologies - improvement
 *******************************************************************************/

package org.eclipse.mylyn.internal.tasks.ui.wizards;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.tasks.core.data.TaskAttachmentMapper;
import org.eclipse.mylyn.tasks.core.data.TaskAttachmentModel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Text;

/**
 * Shows a preview of an attachment.
 * 
 * @author Jeff Pound
 * @author Steffen Pingel
 */
public class AttachmentPreviewPage extends WizardPage {

	private static final String DIALOG_SETTING_RUN_IN_BACKGROUND = "run-in-background"; //$NON-NLS-1$

	private static final String DIALOG_SETTINGS_SECTION_ATTACHMENTS_WIZARD = "attachments-wizard"; //$NON-NLS-1$

	protected static final int MAX_TEXT_SIZE = 50000;

	private static final String PAGE_NAME = "PreviewAttachmentPage"; //$NON-NLS-1$

	private final Set<String> imageTypes;

	private final TaskAttachmentModel model;

	private Button runInBackgroundButton;

	private ScrolledComposite scrolledComposite;

	private final Set<String> textTypes;

	private Composite contentComposite;

	public AttachmentPreviewPage(TaskAttachmentModel model) {
		super(PAGE_NAME);
		this.model = model;
		setTitle(Messages.AttachmentPreviewPage_Attachment_Preview);
		setDescription(Messages.AttachmentPreviewPage_Review_the_attachment_before_submitting);

		textTypes = new HashSet<String>();
		textTypes.add("text/plain"); //$NON-NLS-1$
		textTypes.add("text/html"); //$NON-NLS-1$
		textTypes.add("application/xml"); //$NON-NLS-1$

		imageTypes = new HashSet<String>();
		imageTypes.add("image/jpeg"); //$NON-NLS-1$
		imageTypes.add("image/gif"); //$NON-NLS-1$
		imageTypes.add("image/png"); //$NON-NLS-1$
		imageTypes.add("image/tiff"); //$NON-NLS-1$
	}

	private void adjustScrollbars(Rectangle imgSize) {
		Rectangle clientArea = scrolledComposite.getClientArea();

		ScrollBar hBar = scrolledComposite.getHorizontalBar();
		hBar.setMinimum(0);
		hBar.setMaximum(imgSize.width - 1);
		hBar.setPageIncrement(clientArea.width);
		hBar.setIncrement(10);

		ScrollBar vBar = scrolledComposite.getVerticalBar();
		vBar.setMinimum(0);
		vBar.setMaximum(imgSize.height - 1);
		vBar.setPageIncrement(clientArea.height);
		vBar.setIncrement(10);
	}

	public void createControl(Composite parent) {
		final Composite composite = new Composite(parent, SWT.NONE);
		composite.setLayout(new GridLayout());
		setControl(composite);

		contentComposite = new Composite(composite, SWT.NONE);
		contentComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
		contentComposite.setLayout(new GridLayout());

		runInBackgroundButton = new Button(composite, SWT.CHECK);
		runInBackgroundButton.setText(Messages.AttachmentPreviewPage_Run_in_background);

		IDialogSettings settings = TasksUiPlugin.getDefault().getDialogSettings();
		IDialogSettings attachmentsSettings = settings.getSection(DIALOG_SETTINGS_SECTION_ATTACHMENTS_WIZARD);
		if (attachmentsSettings != null) {
			runInBackgroundButton.setSelection(attachmentsSettings.getBoolean(DIALOG_SETTING_RUN_IN_BACKGROUND));
		}

		Dialog.applyDialogFont(composite);
	}

	@Override
	public void dispose() {
		IDialogSettings settings = TasksUiPlugin.getDefault().getDialogSettings();
		IDialogSettings attachmentsSettings = settings.getSection(DIALOG_SETTINGS_SECTION_ATTACHMENTS_WIZARD);
		if (attachmentsSettings == null) {
			attachmentsSettings = settings.addNewSection(DIALOG_SETTINGS_SECTION_ATTACHMENTS_WIZARD);
		}
		attachmentsSettings.put(DIALOG_SETTING_RUN_IN_BACKGROUND, runInBackgroundButton.getSelection());
		super.dispose();
	}

	@Override
	public void setVisible(boolean visible) {
		if (visible) {
			Control[] children = contentComposite.getChildren();
			for (Control control : children) {
				control.dispose();
			}
			if (isTextAttachment() || isImageAttachment()) {
				Object content = getContent(contentComposite);
				if (content instanceof String) {
					createTextPreview(contentComposite, (String) content);
				} else if (content instanceof Image) {
					createImagePreview(contentComposite, (Image) content);
				}
			} else {
				createGenericPreview(contentComposite);
			}
			contentComposite.layout(true, true);
		}
		Dialog.applyDialogFont(contentComposite);
		super.setVisible(visible);
	}

	private void createErrorPreview(Composite composite, String message) {
		Label label = new Label(composite, SWT.NONE);
		label.setLayoutData(new GridData(GridData.FILL_BOTH));
		label.setText(message);
	}

	private void createGenericPreview(Composite composite) {
		Label label = new Label(composite, SWT.NONE);
		label.setLayoutData(new GridData(GridData.FILL_BOTH));
		// TODO 3.5 put filename on model
		String name = model.getSource().getName();
		TaskAttachmentMapper taskAttachment = TaskAttachmentMapper.createFrom(model.getAttribute());
		if (taskAttachment.getFileName() != null) {
			name = taskAttachment.getFileName();
		}
		label.setText(MessageFormat.format(
				Messages.AttachmentPreviewPage_A_preview_the_type_X_is_currently_not_available, name,
				model.getContentType()));
	}

	private void createImagePreview(Composite composite, final Image bufferedImage) {
		scrolledComposite = new ScrolledComposite(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
		scrolledComposite.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
		scrolledComposite.setExpandHorizontal(true);
		scrolledComposite.setExpandVertical(true);

		Composite canvasComposite = new Composite(scrolledComposite, SWT.NONE);
		canvasComposite.setLayout(GridLayoutFactory.fillDefaults().create());
		Canvas canvas = new Canvas(canvasComposite, SWT.NO_BACKGROUND);
		final Rectangle imgSize = bufferedImage.getBounds();
		canvas.setLayoutData(GridDataFactory.fillDefaults()
				.align(SWT.CENTER, SWT.CENTER)
				.grab(true, true)
				.hint(imgSize.width, imgSize.height)
				.create());
		canvas.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent event) {
				event.gc.drawImage(bufferedImage, 0, 0);
			}
		});
		canvas.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent event) {
				bufferedImage.dispose();
			}
		});
		canvas.setSize(imgSize.width, imgSize.height);
		scrolledComposite.setMinSize(imgSize.width, imgSize.height);
		scrolledComposite.setContent(canvasComposite);
		scrolledComposite.addControlListener(new ControlAdapter() {
			@Override
			public void controlResized(ControlEvent event) {
				adjustScrollbars(imgSize);
			}

		});
		adjustScrollbars(imgSize);
	}

	private void createTextPreview(Composite composite, String contents) {
		Text text = new Text(composite, SWT.MULTI | SWT.READ_ONLY | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
		GridData gd = new GridData(GridData.FILL_BOTH);
		gd.heightHint = composite.getBounds().y;
		gd.widthHint = composite.getBounds().x;
		text.setLayoutData(gd);
		text.setText(contents);
	}

	private Object getContent(final Composite composite) {
		final Object result[] = new Object[1];
		try {
			getContainer().run(true, false, new IRunnableWithProgress() {
				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
					try {
						monitor.beginTask(Messages.AttachmentPreviewPage_Preparing_preview, IProgressMonitor.UNKNOWN);
						final InputStream in = model.getSource().createInputStream(monitor);
						try {
							if (isTextAttachment()) {
								StringBuilder content = new StringBuilder();
								BufferedReader reader = new BufferedReader(new InputStreamReader(in));
								String line;
								while ((line = reader.readLine()) != null && content.length() < MAX_TEXT_SIZE
										&& !monitor.isCanceled()) {
									content.append(line);
									content.append("\n"); //$NON-NLS-1$
								}
								result[0] = content.toString();
							} else if (isImageAttachment()) {
								Display.getDefault().syncExec(new Runnable() {
									public void run() {
										// Uses double buffering to paint the image; there was a weird behavior
										// with transparent images and flicker with large images
										Image originalImage = new Image(getShell().getDisplay(), in);
										final Image bufferedImage = new Image(getShell().getDisplay(),
												originalImage.getBounds());
										GC gc = new GC(bufferedImage);
										gc.setBackground(composite.getBackground());
										gc.fillRectangle(originalImage.getBounds());
										gc.drawImage(originalImage, 0, 0);
										gc.dispose();
										originalImage.dispose();
										result[0] = bufferedImage;
									}
								});
							}
						} catch (IOException e) {
							throw new InvocationTargetException(e);
						} finally {
							try {
								in.close();
							} catch (IOException e) {
								StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
										"Failed to close file", e)); //$NON-NLS-1$
							}
						}
					} catch (CoreException e) {
						throw new InvocationTargetException(e);
					} finally {
						monitor.done();
					}
				}
			});
		} catch (InvocationTargetException e) {
			StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Error generating preview", e)); //$NON-NLS-1$
			createErrorPreview(composite, Messages.AttachmentPreviewPage_Could_not_create_preview);
			return null;
		} catch (InterruptedException e) {
			return null;
		}
		return result[0];
	}

	private boolean isImageAttachment() {
		return imageTypes.contains(model.getContentType());
	}

	private boolean isTextAttachment() {
		return textTypes.contains(model.getContentType());
	}

	public boolean runInBackground() {
		return runInBackgroundButton.getSelection();
	}

}
