/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.vpe.editor.template;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.jboss.tools.common.model.ui.ModelUIImages;
import org.jboss.tools.jst.css.CSSPlugin;
import org.jboss.tools.jst.css.dialog.CSSStyleDialog;
import org.jboss.tools.jst.css.dialog.common.Util;
import org.jboss.tools.jst.jsp.util.Constants;
import org.jboss.tools.vpe.VpePlugin;
import org.jboss.tools.vpe.editor.Message;
import org.jboss.tools.vpe.editor.template.expression.VpeExpressionBuilder;
import org.jboss.tools.vpe.editor.template.expression.VpeExpressionBuilderException;
import org.jboss.tools.vpe.messages.VpeUIMessages;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;

/**
 * Class which presents dialog for any template
 * @author mareshkau
 *
 */
public class VpeEditAnyDialog extends TitleAreaDialog {

	/*
	 * Current template
	 */
	private VpeAnyData data;
	/*
	 * List of already created templates
	 */
	List<VpeAnyData> tagsList;
	Map<String, VpeAnyData> tagsNamesSet = new HashMap<String, VpeAnyData>();
	Text tagName;
	Text tagUri;
	private String previousUri = Constants.EMPTY;
	private Button childrenCheckbox;
	private Text txtTagForDisplay;
	private Text txtValue;
	private Text txtStyle;
    private VpeEditAnyDialogValidator templateVerifier;

	public VpeEditAnyDialog(Shell shell, VpeAnyData data, List<VpeAnyData> tagsList) {
		super(shell);
		this.data = data;
		this.tagsList = tagsList; 
		if (null != tagsList) {
			for (VpeAnyData tag : tagsList) {
				tagsNamesSet.put(tag.getName(), tag);
			}
		}
		setHelpAvailable(false);
	}

	@Override
	protected Control createDialogArea(Composite parent) {
		/*
		 * Setting dialog Title, Message, Image.
		 */
		getShell().setText(VpeUIMessages.TEMPLATE);
		setTitle(VpeUIMessages.TAG_ATTRIBUTES);
		setTitleImage(ModelUIImages.getImage(ModelUIImages.WIZARD_DEFAULT)); //image is managed by registry
		setMessage(VpeUIMessages.USER_SPECIFIED_TAG_DIALOG_DESCRIPTION);
		
		/*
		 * Create validator
		 */
		templateVerifier = new VpeEditAnyDialogValidator();
		
		/*
		 * Create composites.
		 */
		Composite topComposite = (Composite)super.createDialogArea(parent);
		((GridData)topComposite.getLayoutData()).widthHint = 300;

		final Composite composite = new Composite(topComposite, SWT.NONE);
		GridLayout gridLayout = new GridLayout(3, false);
		gridLayout.marginWidth = 50;
		gridLayout.marginHeight = 20;
		gridLayout.horizontalSpacing = 5;
		composite.setLayout(gridLayout);
		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		composite.setBackground(parent.getBackground());
		composite.setForeground(parent.getForeground());
		composite.setFont(parent.getFont());
		
		/*
		 * Create Tag Name label
		 */
		Label tagNameLabel = new Label(composite, SWT.NONE);
		tagNameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.NONE, false, false, 1, 1));
		tagNameLabel.setText(VpeUIMessages.TAG_NAME);
		
		/*
		 * Create Tag Name value
		 */
		tagName = new Text(composite, SWT.BORDER);
		tagName.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1));
		String text = Constants.EMPTY;
		if ((data != null) && (data.getName() != null)){
			text = data.getName();
		}
		tagName.setText(text);
		tagName.addModifyListener(templateVerifier);
		
		/*
		 * Create Tag URI label
		 */
		Label tagUriLabel = new Label(composite, SWT.NONE);
		tagUriLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.NONE, false, false, 1, 1));
		tagUriLabel.setText(VpeUIMessages.TAG_URI);
		
		/*
		 * Create Tag URI value
		 */
		tagUri = new Text(composite, SWT.BORDER);
		tagUri.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1));
		text = Constants.EMPTY;
		if ((data != null) && (data.getUri() != null)) {
			text = data.getUri();
		}
		tagUri.setText(text);
		tagUri.addModifyListener(templateVerifier);

		/*
		 * Create Tag for display label
		 */
        Label tagForDisplayLabel = new Label(composite, SWT.NONE); 
        tagForDisplayLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.NONE, false, false, 1, 1));
        tagForDisplayLabel.setText(VpeUIMessages.TAG_FOR_DISPLAY);
        
        /*
		 * Create Tag for display value
		 */
        txtTagForDisplay = new Text(composite, SWT.BORDER);
        txtTagForDisplay.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1));
        text = Constants.EMPTY;
		if ((data != null) && (data.getTagForDisplay() != null)) {
			text = data.getTagForDisplay();
		}
        txtTagForDisplay.setText(text);
        txtTagForDisplay.addModifyListener(templateVerifier);
        
        /*
         * Create Children label
         */
        Label childrenCheckboxLabel = new Label(composite, SWT.NONE);
        childrenCheckboxLabel.setLayoutData(new GridData(SWT.LEFT, SWT.NONE, false, false, 1, 1));
        childrenCheckboxLabel.setText(VpeUIMessages.CHILDREN);
		
        /*
         * Create check box for Children
         */
        childrenCheckbox = new Button(composite, SWT.CHECK);
        childrenCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.NONE, true, false, 2, 1));
        childrenCheckbox.setSelection(data.isChildren());
        
		/*
		 * Create value label
		 */
		Label lblValue = new Label(composite, SWT.NONE); 
		lblValue.setLayoutData(new GridData(SWT.RIGHT, SWT.NONE, false, false, 1, 1));
		lblValue.setText( VpeUIMessages.VALUE);
		
		/*
		 * Create value
		 */
		txtValue = new Text(composite, SWT.BORDER);
		txtValue.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1));
		text = Constants.EMPTY;
		if ((data != null) && (data.getValue() != null)) {
			text = data.getValue();
		}
		txtValue.setText(text);
		txtValue.addModifyListener(templateVerifier);

		/*
		 * Create style label
		 */
		Label lbStyle = new Label(composite, SWT.NONE); 
		lbStyle.setLayoutData(new GridData(SWT.RIGHT, SWT.NONE, false, false, 1, 1));
		lbStyle.setText(VpeUIMessages.TAG_STYLE);
		
		/*
		 * Create style value
		 */
		txtStyle =  new Text(composite, SWT.BORDER);
		txtStyle.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 1, 1));
		 text = Constants.EMPTY;
		if ((data != null) && (data.getStyle() != null)) {
			text = data.getStyle();
		}
		txtStyle.setText(text);
	
		/*
		 * Create style button
		 */
		Button button = new Button(composite, SWT.PUSH);
		button.setLayoutData(new GridData(SWT.LEFT, SWT.NONE, false, false, 1, 1));
		button.setToolTipText(VpeUIMessages.EDIT_STYLE_TIP);
		ImageDescriptor colorDesc = CSSPlugin
			.getImageDescriptor(Util.IMAGE_COLORLARGE_FILE_LOCATION);
		Image im = colorDesc.createImage();
		button.setImage(im);
		
		button.addDisposeListener(new DisposeListener() {
		    public void widgetDisposed(DisposeEvent e) {
			Button button = (Button) e.getSource();
			button.getImage().dispose();
		    }
		});
		button.addSelectionListener(new SelectionAdapter() {
		    @Override
			public void widgetSelected(SelectionEvent event) {
		    	CSSStyleDialog cssDialog = new CSSStyleDialog(composite.getShell(),txtStyle.getText());
				if (cssDialog.open() == Window.OK) {
				    txtStyle.setText(cssDialog.getStyle());
				}
		    }
		});

		return composite;
	}

	@Override
	public void create() {
		super.create();
		templateVerifier.validateAll(false);
	}

	@Override
	protected void okPressed() {
		boolean isChanged = false;
		if ((data.isChildren() != childrenCheckbox.getSelection())
				|| isChanged(data, data.getName(), tagName.getText())
				|| isChanged(data, data.getUri(), tagUri.getText())
				|| isChanged(data, data.getTagForDisplay(), txtTagForDisplay.getText())
				|| isChanged(data, data.getValue(), txtValue.getText())
				|| isChanged(data, data.getStyle(), txtStyle.getText())) {
			isChanged = true;
		}
		
		data.setChanged(isChanged);
		
		data.setChildren(childrenCheckbox.getSelection());
		data.setName(tagName.getText().trim());
		data.setUri(tagUri.getText().trim());
		data.setTagForDisplay(txtTagForDisplay.getText().trim());
		data.setValue(txtValue.getText().trim());
		data.setStyle(txtStyle.getText());
		
		super.okPressed();
	}

	private boolean isChanged(VpeAnyData data, String oldValue, String newValue) {
		boolean isChanged = false;
		if (oldValue == null) {
			oldValue = Constants.EMPTY;
		}
		if (newValue == null) {
			newValue = Constants.EMPTY;
		}
		if (data.isCaseSensitive()) {
			isChanged = !oldValue.trim().equals(newValue.trim());
		} else {
			isChanged = !oldValue.trim().equalsIgnoreCase(newValue.trim());
		}
		return isChanged;
	}

	/**
	 * Sets the message for this dialog with an indication of what type of
	 * message it is.
	 * <p>
	 * @param message the message, or <code>null</code> to clear the message
	 */
	public void setMessage(IMessageProvider message) {
		if (message == null) {
			setMessage(null, IMessageProvider.NONE);
		} else {
			setMessage(message.getMessage(), message.getMessageType());			
		}
	}

	/**
	 * Validator of {@link VpeEditAnyDialog}.
	 * 
	 * @author yradtsevich
	 */
	private class VpeEditAnyDialogValidator implements ModifyListener {
		/**
		 * Used to validate tag-names.
		 */
		private Document xmlDocument = null;
		public VpeEditAnyDialogValidator() {
			try {
				xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
			} catch (ParserConfigurationException e) {
				VpePlugin.getPluginLog().logError(e);
			}
		}
		
		/**
		 * Validates tag name.
		 * <p>
		 * Prefix should exist. 
		 * <p>
		 * Otherwise template won't be saved correctly to auto-templates.xml.
		 * 
		 * @return message is validation failed, null otherwise.
		 */
		private IMessageProvider validateTagName() {
			Message message = null;
			String name = tagName.getText().trim();
			String[] parts = name.split(":"); //$NON-NLS-1$
			Pattern p = Pattern.compile("([a-zA-Z]+\\d*)+"); //$NON-NLS-1$
			/*
			 * Reset editable property for template URI
			 * after it has been disabled.
			 */
			if (!tagUri.getEditable()) {
				tagUri.removeModifyListener(templateVerifier);
				if ((null != data) && (null != data.getName()) &&
						data.getName().split(":")[0].equalsIgnoreCase(parts[0])) { //$NON-NLS-1$
					/*
					 * Restore initial URI
					 */
					previousUri = data.getUri();
				} 
				tagUri.setText(previousUri);
				tagUri.setEditable(true);
				tagUri.addModifyListener(templateVerifier);
			}
			/*
			 * Check if tag's name is correct
			 */
			if (parts.length != 2 || name.startsWith(":") //$NON-NLS-1$
					|| name.endsWith(":")) { //$NON-NLS-1$
				message = new Message(
						MessageFormat.format(VpeUIMessages.TAG_NAME_IS_NOT_VALID,
								name), IMessageProvider.ERROR);
			} else if ((parts[0].length() == 0) || (parts[1].length() == 0)
						|| (!p.matcher(parts[0]).matches()) 
						|| (!p.matcher(parts[1]).matches())) {
					/*
					 * Matcher will accept only word characters with optional numbers.
					 */
					message = new Message(
							MessageFormat.format(VpeUIMessages.TAG_NAME_IS_NOT_VALID,
									name), IMessageProvider.ERROR);
			} else if (tagsNamesSet.keySet().contains(name)
					&& (!tagsList.contains(data) 
						|| (tagsList.contains(data) && !data.getName().equalsIgnoreCase(name)))) {
				/*
				 * Find duplicate tag names.
				 */
				message = new Message(
						MessageFormat.format(VpeUIMessages.TAG_NAME_ALREADY_EXISTS,
								name), IMessageProvider.ERROR);
			} else {
				/*
				 * Inform that URI for the specified taglib namespace
				 * is already defined in another templates.
				 */
				for (String templateName : tagsNamesSet.keySet()) {
					if (parts[0].equalsIgnoreCase(templateName.split(":")[0]) //$NON-NLS-1$
							&& (!tagsList.contains(data) || (tagsList.contains(data) && !data.getName().split(":")[0].equalsIgnoreCase(parts[0])))) { //$NON-NLS-1$
						message = new Message(
						MessageFormat.format(VpeUIMessages.URI_TAGLIB_NAMESPACE_ALREADY_DEFINED,
								parts[0], tagsNamesSet.get(templateName).getUri()), IMessageProvider.WARNING);
						/*
						 * Set the URI and disable this field.
						 * Remove the listener to avoid dead lock
						 */
						tagUri.removeModifyListener(templateVerifier);
						previousUri = tagUri.getText();
						tagUri.setText(tagsNamesSet.get(templateName).getUri());
						tagUri.setEditable(false);
						tagUri.addModifyListener(templateVerifier);
						break;
					}
				}
			}
			return message;
		}

		/**
		 * Validates {@link VpeEditAnyDialog#txtTagForDisplay} field.
		 *
		 * @return {@code null} if it is valid or an instance of {@code IMessageProvider} if
		 * it contains any error.
		 */
		private IMessageProvider validateTagForDisplay() {
			if (xmlDocument != null) {
				try {
					xmlDocument.createElement(txtTagForDisplay.getText());
				} catch (DOMException e) {
					/*
					 * https://jira.jboss.org/browse/JBIDE-6599
					 * Changing warning message.
					 */
					return new Message(
							MessageFormat.format(VpeUIMessages.TAG_FOR_DISPLAY_IS_NOT_VALID,
									txtTagForDisplay.getText()),
							IMessageProvider.ERROR);
				}
			}

			return null;
		}

		/**
		 * Validates {@link VpeEditAnyDialog#txtValue} field.
		 *
		 * @return {@code null} if it is valid or an instance of {@code IMessageProvider} if
		 * it contains any error.
		 */
		private IMessageProvider validateValue() {

			try {
				VpeExpressionBuilder.buildCompletedExpression(txtValue.getText(), true);
			} catch (VpeExpressionBuilderException e) {
				return new Message(
						MessageFormat.format(VpeUIMessages.VALUE_IS_NOT_VALID,
											e.getMessage()),
						IMessageProvider.ERROR);
			}

			return null;
		}

		/**
		 * Validates all fields of {@link VpeEditAnyDialog} and changes the view of
		 * dialog according to validation results.
		 * 
		 * @param updateMessage if it is {@code true}, the dialog's message will be updated.
		 */
		void validateAll(boolean updateMessage) {
			/*
			 * Initialize the message with the description
			 */
			IMessageProvider message = new Message(VpeUIMessages.USER_SPECIFIED_TAG_DIALOG_DESCRIPTION,
					IMessageProvider.NONE);
			List<IMessageProvider> statuses = new ArrayList<IMessageProvider>();
			/*
			 * Get messages from all validators
			 */
			statuses.add(validateTagForDisplay());
			statuses.add(validateValue());
			statuses.add(validateTagName());
			/*
			 * Find the message with the most severe status
			 */
			for (IMessageProvider status : statuses) {
				if (null != status) {
					message = message.getMessageType() >= status.getMessageType()
					? message : status;
				}
			}
			String messageText = message.getMessage();
			switch (message.getMessageType()) {
			case IMessageProvider.NONE:
				setMessage(messageText, IMessageProvider.NONE);
				setErrorMessage(null);
				break;

			case IMessageProvider.WARNING:
				setMessage(messageText, IMessageProvider.WARNING);
				setErrorMessage(null);
				break;

			case IMessageProvider.INFORMATION:
				setMessage(messageText, IMessageProvider.INFORMATION);
				setErrorMessage(null);
				break;

			default:
				/*
				 * Set ERROR message
				 */
				if (messageText.length() == 0) {
					messageText = null;
				}
				setMessage(null, IMessageProvider.NONE);
				setErrorMessage(messageText);
				break;
			} // end switch
			
			Button okButton = getButton(IDialogConstants.OK_ID);
			if ((message == null) || (message.getMessageType() <= IMessageProvider.WARNING)) {
				okButton.setEnabled(true);
			} else {
				okButton.setEnabled(false);
			}

			if (updateMessage) {
				VpeEditAnyDialog.this.setMessage(message);
			}
		}

		/**
		 * Fired when a field is modified.
		 */
		public void modifyText(ModifyEvent e) {
			validateAll(true);
		}
	}
}
