/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package net.sourceforge.phpdt.internal.ui.preferences;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.sourceforge.phpdt.internal.corext.template.php.CodeTemplateContextType;
import net.sourceforge.phpdt.internal.ui.text.IPHPPartitions;
import net.sourceforge.phpdt.internal.ui.text.template.preferences.TemplateVariableProcessor;
import net.sourceforge.phpdt.internal.ui.util.PixelConverter;
import net.sourceforge.phpdt.internal.ui.wizards.dialogfields.DialogField;
import net.sourceforge.phpdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import net.sourceforge.phpdt.internal.ui.wizards.dialogfields.ITreeListAdapter;
import net.sourceforge.phpdt.internal.ui.wizards.dialogfields.LayoutUtil;
import net.sourceforge.phpdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField;
import net.sourceforge.phpdt.internal.ui.wizards.dialogfields.TreeListDialogField;
import net.sourceforge.phpdt.ui.PreferenceConstants;
import net.sourceforge.phpdt.ui.text.JavaTextTools;
import net.sourceforge.phpeclipse.PHPeclipsePlugin;
import net.sourceforge.phpeclipse.phpeditor.JavaSourceViewer;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.persistence.TemplatePersistenceData;
import org.eclipse.jface.text.templates.persistence.TemplateReaderWriter;
import org.eclipse.jface.text.templates.persistence.TemplateStore;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

/**
 */
public class CodeTemplateBlock {

	private class CodeTemplateAdapter implements ITreeListAdapter,
			IDialogFieldListener {

		private final Object[] NO_CHILDREN = new Object[0];

		public void customButtonPressed(TreeListDialogField field, int index) {
			doButtonPressed(index, field.getSelectedElements());
		}

		public void selectionChanged(TreeListDialogField field) {
			List selected = field.getSelectedElements();
			field.enableButton(IDX_EDIT, canEdit(selected));
			field.enableButton(IDX_EXPORT, !selected.isEmpty());

			updateSourceViewerInput(selected);
		}

		public void doubleClicked(TreeListDialogField field) {
			List selected = field.getSelectedElements();
			if (canEdit(selected)) {
				doButtonPressed(IDX_EDIT, selected);
			}
		}

		public Object[] getChildren(TreeListDialogField field, Object element) {
			if (element == COMMENT_NODE || element == CODE_NODE) {
				return getTemplateOfCategory(element == COMMENT_NODE);
			}
			return NO_CHILDREN;
		}

		public Object getParent(TreeListDialogField field, Object element) {
			if (element instanceof TemplatePersistenceData) {
				TemplatePersistenceData data = (TemplatePersistenceData) element;
				if (data.getTemplate().getName().endsWith(
						CodeTemplateContextType.COMMENT_SUFFIX)) {
					return COMMENT_NODE;
				}
				return CODE_NODE;
			}
			return null;
		}

		public boolean hasChildren(TreeListDialogField field, Object element) {
			return (element == COMMENT_NODE || element == CODE_NODE);
		}

		public void dialogFieldChanged(DialogField field) {
		}

		public void keyPressed(TreeListDialogField field, KeyEvent event) {
		}

	}

	private static class CodeTemplateLabelProvider extends LabelProvider {

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.ILabelProvider#getImage(java.lang.Object)
		 */
		public Image getImage(Object element) {
			return null;

		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
		 */
		public String getText(Object element) {
			if (element == COMMENT_NODE || element == CODE_NODE) {
				return (String) element;
			}
			TemplatePersistenceData data = (TemplatePersistenceData) element;
			Template template = data.getTemplate();
			String name = template.getName();
			if (CodeTemplateContextType.CATCHBLOCK.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.catchblock.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.METHODSTUB.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.methodstub.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.CONSTRUCTORSTUB.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.constructorstub.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.GETTERSTUB.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.getterstub.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.SETTERSTUB.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.setterstub.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.NEWTYPE.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.newtype.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.TYPECOMMENT.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.typecomment.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.FIELDCOMMENT.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.fieldcomment.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.METHODCOMMENT.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.methodcomment.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.OVERRIDECOMMENT.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.overridecomment.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.CONSTRUCTORCOMMENT.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.constructorcomment.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.GETTERCOMMENT.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.gettercomment.label"); //$NON-NLS-1$
			} else if (CodeTemplateContextType.SETTERCOMMENT.equals(name)) {
				return PreferencesMessages
						.getString("CodeTemplateBlock.settercomment.label"); //$NON-NLS-1$
			}
			return template.getDescription();
		}

	}

	private final static int IDX_EDIT = 0;

	private final static int IDX_IMPORT = 2;

	private final static int IDX_EXPORT = 3;

	private final static int IDX_EXPORTALL = 4;

	protected final static Object COMMENT_NODE = PreferencesMessages
			.getString("CodeTemplateBlock.templates.comment.node"); //$NON-NLS-1$

	protected final static Object CODE_NODE = PreferencesMessages
			.getString("CodeTemplateBlock.templates.code.node"); //$NON-NLS-1$

	private static final String PREF_JAVADOC_STUBS = PreferenceConstants.CODEGEN_ADD_COMMENTS;

	private TreeListDialogField fCodeTemplateTree;

	private SelectionButtonDialogField fCreateJavaDocComments;

	protected TemplateStore fTemplates;

	private PixelConverter fPixelConverter;

	private SourceViewer fPatternViewer;

	private Control fSWTWidget;

	private TemplateVariableProcessor fTemplateProcessor;

	public CodeTemplateBlock() {

		fTemplates = PHPeclipsePlugin.getDefault().getCodeTemplateStore();
		fTemplateProcessor = new TemplateVariableProcessor();

		CodeTemplateAdapter adapter = new CodeTemplateAdapter();

		String[] buttonLabels = new String[] {
				/* IDX_EDIT */PreferencesMessages
						.getString("CodeTemplateBlock.templates.edit.button"), //$NON-NLS-1$
				/* */null,
				/* IDX_IMPORT */PreferencesMessages
						.getString("CodeTemplateBlock.templates.import.button"), //$NON-NLS-1$
				/* IDX_EXPORT */PreferencesMessages
						.getString("CodeTemplateBlock.templates.export.button"), //$NON-NLS-1$
				/* IDX_EXPORTALL */PreferencesMessages
						.getString("CodeTemplateBlock.templates.exportall.button") //$NON-NLS-1$

		};
		fCodeTemplateTree = new TreeListDialogField(adapter, buttonLabels,
				new CodeTemplateLabelProvider());
		fCodeTemplateTree.setDialogFieldListener(adapter);
		fCodeTemplateTree.setLabelText(PreferencesMessages
				.getString("CodeTemplateBlock.templates.label")); //$NON-NLS-1$

		fCodeTemplateTree.enableButton(IDX_EXPORT, false);
		fCodeTemplateTree.enableButton(IDX_EDIT, false);

		fCodeTemplateTree.addElement(COMMENT_NODE);
		fCodeTemplateTree.addElement(CODE_NODE);

		fCreateJavaDocComments = new SelectionButtonDialogField(SWT.CHECK
				| SWT.WRAP);
		fCreateJavaDocComments.setLabelText(PreferencesMessages
				.getString("CodeTemplateBlock.createcomment.label")); //$NON-NLS-1$
		fCreateJavaDocComments.setSelection(PreferenceConstants
				.getPreferenceStore().getBoolean(PREF_JAVADOC_STUBS));

		fCodeTemplateTree.selectFirstElement();
	}

	protected Control createContents(Composite parent) {
		fPixelConverter = new PixelConverter(parent);
		fSWTWidget = parent;

		Composite composite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		layout.numColumns = 2;
		composite.setLayout(layout);

		fCodeTemplateTree.doFillIntoGrid(composite, 3);
		LayoutUtil
				.setHorizontalSpan(fCodeTemplateTree.getLabelControl(null), 2);
		LayoutUtil
				.setHorizontalGrabbing(fCodeTemplateTree.getTreeControl(null));

		fPatternViewer = createViewer(composite, 2);

		fCreateJavaDocComments.doFillIntoGrid(composite, 2);

		DialogField label = new DialogField();
		label.setLabelText(PreferencesMessages
				.getString("CodeTemplateBlock.createcomment.description")); //$NON-NLS-1$
		label.doFillIntoGrid(composite, 2);

		return composite;

	}

	private Shell getShell() {
		if (fSWTWidget != null) {
			return fSWTWidget.getShell();
		}
		return PHPeclipsePlugin.getActiveWorkbenchShell();
	}

	private SourceViewer createViewer(Composite parent, int nColumns) {
		Label label = new Label(parent, SWT.NONE);
		label.setText(PreferencesMessages
				.getString("CodeTemplateBlock.preview")); //$NON-NLS-1$
		GridData data = new GridData();
		data.horizontalSpan = nColumns;
		label.setLayoutData(data);

		IDocument document = new Document();
		JavaTextTools tools = PHPeclipsePlugin.getDefault().getJavaTextTools();
		tools.setupJavaDocumentPartitioner(document,
				IPHPPartitions.PHP_PARTITIONING);
		IPreferenceStore store = PHPeclipsePlugin.getDefault()
				.getCombinedPreferenceStore();
		SourceViewer viewer = new JavaSourceViewer(parent, null, null, false,
				SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL, store);
		TemplateEditorSourceViewerConfiguration configuration = new TemplateEditorSourceViewerConfiguration(
				tools.getColorManager(), store, null, fTemplateProcessor);
		viewer.configure(configuration);
		viewer.setEditable(false);
		viewer.setDocument(document);

		Font font = JFaceResources
				.getFont(PreferenceConstants.EDITOR_TEXT_FONT);
		viewer.getTextWidget().setFont(font);
		new JavaSourcePreviewerUpdater(viewer, configuration, store);

		Control control = viewer.getControl();
		data = new GridData(GridData.HORIZONTAL_ALIGN_FILL
				| GridData.FILL_VERTICAL);
		data.horizontalSpan = nColumns;
		data.heightHint = fPixelConverter.convertHeightInCharsToPixels(5);
		control.setLayoutData(data);

		return viewer;
	}

	protected TemplatePersistenceData[] getTemplateOfCategory(boolean isComment) {
		ArrayList res = new ArrayList();
		TemplatePersistenceData[] templates = fTemplates.getTemplateData(false);
		for (int i = 0; i < templates.length; i++) {
			TemplatePersistenceData curr = templates[i];
			if (isComment == curr.getTemplate().getName().endsWith(
					CodeTemplateContextType.COMMENT_SUFFIX)) {
				res.add(curr);
			}
		}
		return (TemplatePersistenceData[]) res
				.toArray(new TemplatePersistenceData[res.size()]);
	}

	protected static boolean canEdit(List selected) {
		return selected.size() == 1
				&& (selected.get(0) instanceof TemplatePersistenceData);
	}

	protected void updateSourceViewerInput(List selection) {
		if (fPatternViewer == null
				|| fPatternViewer.getTextWidget().isDisposed()) {
			return;
		}
		if (selection.size() == 1
				&& selection.get(0) instanceof TemplatePersistenceData) {
			TemplatePersistenceData data = (TemplatePersistenceData) selection
					.get(0);
			Template template = data.getTemplate();
			TemplateContextType type = PHPeclipsePlugin.getDefault()
					.getCodeTemplateContextRegistry().getContextType(
							template.getContextTypeId());
			fTemplateProcessor.setContextType(type);
			fPatternViewer.getDocument().set(template.getPattern());
		} else {
			fPatternViewer.getDocument().set(""); //$NON-NLS-1$
		}
	}

	protected void doButtonPressed(int buttonIndex, List selected) {
		if (buttonIndex == IDX_EDIT) {
			edit((TemplatePersistenceData) selected.get(0));
		} else if (buttonIndex == IDX_EXPORT) {
			export(selected);
		} else if (buttonIndex == IDX_EXPORTALL) {
			exportAll();
		} else if (buttonIndex == IDX_IMPORT) {
			import_();
		}
	}

	private void edit(TemplatePersistenceData data) {
		Template newTemplate = new Template(data.getTemplate());
		EditTemplateDialog dialog = new EditTemplateDialog(getShell(),
				newTemplate, true, false, PHPeclipsePlugin.getDefault()
						.getCodeTemplateContextRegistry());
		if (dialog.open() == Window.OK) {
			// changed
			data.setTemplate(newTemplate);
			fCodeTemplateTree.refresh(data);
			fCodeTemplateTree.selectElements(new StructuredSelection(data));
		}
	}

	private void import_() {
		FileDialog dialog = new FileDialog(getShell());
		dialog.setText(PreferencesMessages
				.getString("CodeTemplateBlock.import.title")); //$NON-NLS-1$
		dialog.setFilterExtensions(new String[] { PreferencesMessages
				.getString("CodeTemplateBlock.import.extension") }); //$NON-NLS-1$
		String path = dialog.open();

		if (path == null)
			return;

		try {
			TemplateReaderWriter reader = new TemplateReaderWriter();
			File file = new File(path);
			if (file.exists()) {
				InputStream input = new BufferedInputStream(
						new FileInputStream(file));
				TemplatePersistenceData[] datas = reader.read(input, null);
				for (int i = 0; i < datas.length; i++) {
					updateTemplate(datas[i]);
				}
			}

			fCodeTemplateTree.refresh();
			updateSourceViewerInput(fCodeTemplateTree.getSelectedElements());

		} catch (FileNotFoundException e) {
			openReadErrorDialog(e);
		} catch (IOException e) {
			openReadErrorDialog(e);
		}

	}

	private void updateTemplate(TemplatePersistenceData data) {
		TemplatePersistenceData[] datas = fTemplates.getTemplateData(true);
		for (int i = 0; i < datas.length; i++) {
			String id = datas[i].getId();
			if (id != null && id.equals(data.getId())) {
				datas[i].setTemplate(data.getTemplate());
				break;
			}
		}
	}

	private void exportAll() {
		export(fTemplates.getTemplateData(false));
	}

	private void export(List selected) {
		List datas = new ArrayList();
		for (int i = 0; i < selected.size(); i++) {
			Object curr = selected.get(i);
			if (curr instanceof TemplatePersistenceData) {
				datas.add(curr);
			} else {
				TemplatePersistenceData[] cat = getTemplateOfCategory(curr == COMMENT_NODE);
				datas.addAll(Arrays.asList(cat));
			}
		}
		export((TemplatePersistenceData[]) datas
				.toArray(new TemplatePersistenceData[datas.size()]));
	}

	private void export(TemplatePersistenceData[] templates) {
		FileDialog dialog = new FileDialog(getShell(), SWT.SAVE);
		dialog
				.setText(PreferencesMessages
						.getFormattedString(
								"CodeTemplateBlock.export.title", String.valueOf(templates.length))); //$NON-NLS-1$
		dialog.setFilterExtensions(new String[] { PreferencesMessages
				.getString("CodeTemplateBlock.export.extension") }); //$NON-NLS-1$
		dialog.setFileName(PreferencesMessages
				.getString("CodeTemplateBlock.export.filename")); //$NON-NLS-1$
		String path = dialog.open();

		if (path == null)
			return;

		File file = new File(path);

		if (file.isHidden()) {
			String title = PreferencesMessages
					.getString("CodeTemplateBlock.export.error.title"); //$NON-NLS-1$ 
			String message = PreferencesMessages
					.getFormattedString(
							"CodeTemplateBlock.export.error.hidden", file.getAbsolutePath()); //$NON-NLS-1$
			MessageDialog.openError(getShell(), title, message);
			return;
		}

		if (file.exists() && !file.canWrite()) {
			String title = PreferencesMessages
					.getString("CodeTemplateBlock.export.error.title"); //$NON-NLS-1$
			String message = PreferencesMessages
					.getFormattedString(
							"CodeTemplateBlock.export.error.canNotWrite", file.getAbsolutePath()); //$NON-NLS-1$
			MessageDialog.openError(getShell(), title, message);
			return;
		}

		if (!file.exists() || confirmOverwrite(file)) {
			try {
				OutputStream output = new BufferedOutputStream(
						new FileOutputStream(file));
				TemplateReaderWriter writer = new TemplateReaderWriter();
				writer.save(templates, output);
			} catch (IOException e) {
				openWriteErrorDialog(e);
			}
		}

	}

	private boolean confirmOverwrite(File file) {
		return MessageDialog
				.openQuestion(
						getShell(),
						PreferencesMessages
								.getString("CodeTemplateBlock.export.exists.title"), //$NON-NLS-1$
						PreferencesMessages
								.getFormattedString(
										"CodeTemplateBlock.export.exists.message", file.getAbsolutePath())); //$NON-NLS-1$
	}

	public void performDefaults() {
		IPreferenceStore prefs = PHPeclipsePlugin.getDefault()
				.getPreferenceStore();
		fCreateJavaDocComments.setSelection(prefs
				.getDefaultBoolean(PREF_JAVADOC_STUBS));

		fTemplates.restoreDefaults();

		// refresh
		fCodeTemplateTree.refresh();
		updateSourceViewerInput(fCodeTemplateTree.getSelectedElements());
	}

	public boolean performOk(boolean enabled) {
		IPreferenceStore prefs = PreferenceConstants.getPreferenceStore();
		prefs.setValue(PREF_JAVADOC_STUBS, fCreateJavaDocComments.isSelected());
		PHPeclipsePlugin.getDefault().savePluginPreferences();

		try {
			fTemplates.save();
		} catch (IOException e) {
			PHPeclipsePlugin.log(e);
			openWriteErrorDialog(e);
		}
		return true;
	}

	public void performCancel() {
		try {
			fTemplates.load();
		} catch (IOException e) {
			openReadErrorDialog(e);
		}
	}

	private void openReadErrorDialog(Exception e) {
		String title = PreferencesMessages
				.getString("CodeTemplateBlock.error.read.title"); //$NON-NLS-1$

		String message = e.getLocalizedMessage();
		if (message != null)
			message = PreferencesMessages.getFormattedString(
					"CodeTemplateBlock.error.parse.message", message); //$NON-NLS-1$
		else
			message = PreferencesMessages
					.getString("CodeTemplateBlock.error.read.message"); //$NON-NLS-1$
		MessageDialog.openError(getShell(), title, message);
	}

	private void openWriteErrorDialog(Exception e) {
		String title = PreferencesMessages
				.getString("CodeTemplateBlock.error.write.title"); //$NON-NLS-1$
		String message = PreferencesMessages
				.getString("CodeTemplateBlock.error.write.message"); //$NON-NLS-1$
		MessageDialog.openError(getShell(), title, message);
	}

}