/*******************************************************************************
 * 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.common.model.ui.attribute.editor;

import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
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.Label;
import org.eclipse.swt.widgets.Text;
import org.jboss.tools.common.meta.XAttribute;
import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.ui.IValueChangeListener;
import org.jboss.tools.common.model.ui.IValueEditor;
import org.jboss.tools.common.model.ui.IValueProvider;
import org.jboss.tools.common.model.ui.ModelUIPlugin;
import org.jboss.tools.common.model.ui.attribute.IPropertyDescriptorEx;
import org.jboss.tools.common.model.ui.attribute.adapter.DefaultValueAdapter;
import org.jboss.tools.common.model.ui.attribute.adapter.IModelPropertyEditorAdapter;
import org.jboss.tools.common.model.ui.widgets.IWidgetSettings;
import org.jboss.tools.common.model.util.AccessibleClasses;
import org.jboss.tools.common.model.util.AccessibleJava;
import org.jboss.tools.common.model.util.ISimpleTree;
import org.jboss.tools.common.model.util.ModelFeatureFactory;
import org.jboss.tools.common.model.util.ModelImages;

public class JavaChoicerFieldEditor extends ExtendedFieldEditor implements IFieldEditor, IPropertyFieldEditor, IValueEditor, ModifyListener, ISelectionChangedListener {
	protected IPropertyEditor propertyEditor;
	protected IValueChangeListener valueChangeListener;
	protected IValueProvider valueProvider;
	protected IPropertyDescriptorEx description;
	private TreeViewer tree;
	private Text text;
	private String stringValue;	
	private Composite treeAndText;
	private XModel model;
	
	private JavaElement rootElement = new JavaRootElement("Java"); //$NON-NLS-1$
	AccessibleJava.IContextPackageProvider cpp = null;
	
	private static final String ROOT_PATH = "%root%"; //$NON-NLS-1$
	private static final String EMPTY_PATH = ""; //$NON-NLS-1$
	private static final Image ROOT_IMAGE = ModelImages.getImage("images/navigationtree/java.gif"); //$NON-NLS-1$
	private static final Image PACKAGE_IMAGE = JavaPluginImages.get(JavaPluginImages.IMG_OBJS_PACKAGE);
	private static final Image CLASS_IMAGE = JavaPluginImages.get(JavaPluginImages.IMG_OBJS_CLASS);
	
	public JavaChoicerFieldEditor() {}
	
	public JavaChoicerFieldEditor(IWidgetSettings settings) {
		super(settings);
	}

	public JavaChoicerFieldEditor(String name, String labelText, Composite parent) {
		init(name, labelText);
		createControl(parent);
	}

	protected void adjustForNumColumns(int numColumns) {
		Control control = getLabelComposite();
		((GridData)control.getLayoutData()).horizontalSpan = numColumns;
		((GridData)treeAndText.getLayoutData()).horizontalSpan = numColumns - 1;
	}

	protected void doFillIntoGrid(Composite parent, int numColumns) {
		/*
		Control control = createTabbedPane(parent);
		GridData gd = new GridData(GridData.FILL_BOTH);
		gd.horizontalSpan = numColumns;
		//gd.horizontalAlignment = GridData.HORIZONTAL_ALIGN_BEGINNING;
		gd.verticalAlignment = GridData.GRAB_VERTICAL;
		control.setLayoutData(gd);
		*/
		
		Control control = getLabelComposite(parent);
		GridData gd = new GridData();
		gd.horizontalSpan = numColumns;
		//gd.horizontalAlignment = GridData.HORIZONTAL_ALIGN_BEGINNING;
		gd.verticalAlignment = GridData.GRAB_VERTICAL;
		control.setLayoutData(gd);

		treeAndText = getTreeAndTextControl(parent);
		gd = new GridData(GridData.FILL_BOTH);
		//gd.verticalAlignment = GridData.FILL;
		gd.horizontalSpan = numColumns;
		//gd.horizontalSpan = numColumns - 1;
		gd.grabExcessHorizontalSpace = true;
		treeAndText.setLayoutData(gd);
		
	}

	protected void doLoad() {
	}

	protected void doLoadDefault() {
	}

	protected void doStore() {
	}

	public int getNumberOfControls() {
		return 2;
	}

	protected Control createFavoritesChooser(Composite parent) {
		return getTreeAndTextControl(parent);
	}

	protected Composite getTreeAndTextControl(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout(1, false);
		composite.setLayout(layout);
		layout.horizontalSpacing = 7;
		layout.verticalSpacing = 7;
		layout.marginHeight = 10;
		layout.marginWidth = 10;
		
		GridData gd;
		Label label;
		
		label = new Label(composite, SWT.NONE);
		label.setText(EditorMessages.getString("JavaChoicerFieldEditor.Tab1.Tree.Label")); //$NON-NLS-1$
		
		tree = new TreeViewer(composite,SWT.BORDER | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL);
		gd = new GridData(GridData.FILL_BOTH);
		//gd.verticalAlignment = GridData.FILL_VERTICAL;
		tree.getControl().setLayoutData(gd);
		tree.setContentProvider(new ContentProviderEx());
		tree.setLabelProvider(new LabelProviderEx());
		tree.setInput("Java"); //$NON-NLS-1$
		if ((this.stringValue!=null)&&(this.stringValue.length()>0)) {
			ISelection selection = getSelection(this.stringValue);
			tree.setSelection(selection); 		
		}
		tree.addSelectionChangedListener(this);
		tree.setAutoExpandLevel(2);

		tree.getControl().addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent e) {
				if(getOwnerDialog() != null) {
					getOwnerDialog().okPressed();
				}
			}
		});

		label = new Label(composite, SWT.NONE);
		label.setText(EditorMessages.getString("JavaChoicerFieldEditor.Tab1.Text.Label")); //$NON-NLS-1$

		text = new Text(composite,SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
		gd = new GridData(GridData.FILL_HORIZONTAL);
		text.setLayoutData(gd);
		text.setText(valueProvider.getStringValue(true));
		text.setSelection(valueProvider.getStringValue(true).length());
		text.addModifyListener(this);
		text.setFocus();
		return composite;
	}
	
	private Object getObject(String path) {
		StringTokenizer st = new StringTokenizer(path, "."); //$NON-NLS-1$
		JavaElement element = rootElement;
		String find = ""; //$NON-NLS-1$
		while (st.hasMoreTokens()) {
			find = st.nextToken();
			find = (element.getPath().equals(rootElement.getPath()))?find:element.getPath()+"."+find; //$NON-NLS-1$
			if (element.getChildByPath(find) == null) {
				// try find like element startWith()
				int childCount = element.getChildCount();
				for (int i = 0; i < childCount; ++i) {
					if (element.getChildAt(i).getPath().startsWith(find)) {
						return element.getChildAt(i); 
					}
				}
				return null;//new StructuredSelection(new Object[] {element});
			}
			element = element.getChildByPath(find);
		}
		return element;
	}
	private ISelection getSelection(String path) {
		Object o = getObject(path);
		if(o == null && cpp != null && path != null && path.length() > 0 && path.indexOf('.') < 0) {
			String pkg = cpp.getContextPackage();
			if(pkg != null && pkg.length() > 0) {
				o = getObject(pkg + "." + path); //$NON-NLS-1$
			}
		}
		return new StructuredSelection(new Object[] {o});
	}
	
	
	protected String oldValue = null;

	protected void valueChanged(String newValue) {
		if ((newValue != null) && (!newValue.equals(oldValue))) {
			text.removeModifyListener(this);
			if ((text.getText()!=null)&&(!text.getText().equals(newValue))) {
				text.setText(newValue);
				text.setSelection(newValue.length());
			}
			PropertyChangeEvent event = new PropertyChangeEvent(this, IPropertyEditor.VALUE, oldValue, newValue);
			valueChangeListener.valueChange(event);
			oldValue = newValue;
			this.stringValue = newValue;
			text.addModifyListener(this);
		}
	}


	protected void handleMouseUp(MouseEvent e) {
	}

	ISimpleTree list = null;

	class ContentProviderEx implements ITreeContentProvider {
		
		public ContentProviderEx() {
///			if(model == null) throw new RuntimeException("Model is not provided for java class chooser.");
			list = new AccessibleClasses(model, true);
		}
		
		public Object[] getChildren(Object parentElement) {
			return ((JavaElement)parentElement).getChildrens();
		}
		public Object getParent(Object element) {
			if (element instanceof JavaElement) {
				return ((JavaElement)element).getParent();
			} else {
				return null;
			}
		}
		public boolean hasChildren(Object element) {
			return ((JavaElement)element).getChildCount()>0;
		}

		public Object[] getElements(Object inputElement) {
			rootElement.check();
			return rootElement.getChildrens();
		}

		public void dispose() {
			list = null;
		}
		
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}
	} 
	class LabelProviderEx extends LabelProvider {

		public Image getImage(Object element) {
			JavaElement javaElement = (JavaElement)element;
			if (ROOT_PATH.equals(javaElement.getPath())) {
				return ROOT_IMAGE;
			} else if (javaElement.isPackage()) {
				return PACKAGE_IMAGE;
			} else {
				return CLASS_IMAGE;
			}
		}

		public String getText(Object element) {
			return super.getText(element);
		}
	}
	class JavaRootElement extends JavaElement {
		public JavaRootElement(String name) {
			path = ROOT_PATH;
			this.name = name;
		}

		protected String _getPath() {
			return null;
		}
	}
	class JavaElement {
		protected JavaElement parent = null;
		private boolean isLoaded = false;
		protected String path = ""; //$NON-NLS-1$
		protected String name = ""; //$NON-NLS-1$
		protected boolean isPackage = false;
		private List<JavaElement> children = new ArrayList<JavaElement>();

		protected JavaElement() {
		}

		public JavaElement(String parentpath, String name) {
			this.path = (parentpath == null) ? name :  parentpath + "." + name; //$NON-NLS-1$
			this.name = name;
		}

		public boolean isPackage() {
			return isPackage;
		}

		public JavaElement createChild(String name) {
			JavaElement c = new JavaElement(_getPath(), name);
			c.parent = this;
			return c;
		}
		
		public Object[] getChildrens() {
			return children.toArray(new Object[children.size()]);
		}

		public String getPath() {
			return path;
		}

		protected String _getPath() {
			return path;
		}
		
		public JavaElement getChildByPath(String path) {
			check();
			JavaElement element;
			for (int i = 0; i < children.size(); ++i) {
				element = children.get(i);
				if (element.getPath().equals(path)) {
					return element;
				}
			}
			return null;
		}

		protected void check() {
			if(isLoaded) return;
			isLoaded = true;
			String[] ns = list.getContent(path);
			if(ns == null) return;
			for (int i = 0; i < ns.length; i++) {
				String s = ns[i];
				boolean p = s.endsWith("."); //$NON-NLS-1$
				if(p) s = s.substring(0, s.length() - 1);
				if(!isJavaName(s)) continue;
				JavaElement c = createChild(s);
				c.isPackage = p;
				children.add(c);
			}
		}

	    boolean isJavaName(String name) {
	    	if(name.length() == 0) return false;
	    	if(!Character.isJavaIdentifierStart(name.charAt(0))) return false;
	    	for (int i = 1; i < name.length(); i++) {
	        	if(!Character.isJavaIdentifierPart(name.charAt(i))) return false;
	    	}
	    	return true;
	    }

		public JavaElement getChildAt(int childIndex) {
			check();
			return children.get(childIndex);
		}

		public int getChildCount() {
			check();
			return children.size();
		}

		public JavaElement getParent() {
			return parent;
		}

		public int getIndex(JavaElement node) {
			check();
			for (int i = 0; i < children.size(); i++)
				if(children.get(i) == node) return i;
			return -1;
		}

		public boolean getAllowsChildren() {
			return true;
		}

		public boolean isLeaf() {
			check();
			return getChildCount() == 0;
		}

		public Iterator<JavaElement> children() {
			return children.iterator();
		}

		public String toString() {
			return name;
		}
	} 

	// IValueEditor
	public void setValueChangeListener(IValueChangeListener valueChangeListener) {
		this.valueChangeListener = valueChangeListener;
	}
	public void setValueProvider(IValueProvider valueProvider) {
		this.valueProvider = valueProvider;
	}

	// ModifyListener
	public void modifyText(ModifyEvent e) {
		String newValue = ((Text)e.getSource()).getText(); 
		tree.removeSelectionChangedListener(this);
		if (newValue.endsWith(" ")) { //$NON-NLS-1$
			StructuredSelection selection = (StructuredSelection)tree.getSelection();
			JavaElement element = (JavaElement)selection.getFirstElement();
			if (element != null) {
				if (!ROOT_PATH.equals(element.getPath())) {
					valueChanged(element.getPath());
				} else {
					valueChanged(EMPTY_PATH);
				}
				newValue = stringValue;
				if(newValue == null) newValue = ""; //$NON-NLS-1$
				text.setText(newValue);
				text.setSelection(newValue.length());
			}
		} else {
			valueChanged(newValue.trim());
		}
		tree.setSelection(getSelection(((Text)e.getSource()).getText()));
		Object currentObject = getObject(((Text)e.getSource()).getText());
		if (currentObject!=null) { 
			tree.expandToLevel(currentObject,1);
		}
		tree.addSelectionChangedListener(this);
	}
	
	protected void init() {
		this.stringValue = valueProvider.getStringValue(true);
		//setPropertyChangeListener(this);
	}

	public void setEnabled(boolean enabled){
		// TODO;
		super.setEnabled(enabled);
	}

	// IPropertyFieldEditor
	public void setPropertyEditor(IPropertyEditor propertyEditor) {
		this.propertyEditor = propertyEditor;
		if (propertyEditor!=null) {
			valueProvider = (IValueProvider)propertyEditor.getAdapter(IValueProvider.class);
			valueChangeListener = (IValueChangeListener)propertyEditor.getAdapter(IValueChangeListener.class);
			Object input = propertyEditor.getInput();
			if(input instanceof DefaultValueAdapter) {
				model = ((DefaultValueAdapter)input).getModel();
				XModelObject xmo = ((DefaultValueAdapter)valueProvider).getModelObject();
				XAttribute attr = ((DefaultValueAdapter)valueProvider).getAttribute();
				if (attr!=null) {
					
					AccessibleJava.IContextPackageProvider cpp = getPackageProvider();
					if(cpp != null) {
						this.cpp = cpp;
						cpp.setObject(xmo);
					}
					
				}
			}
		}
		init();
	}

	// IFieldEditor
	public Control[] getControls(Composite parent) {
		return new Control[] {getTreeAndTextControl(parent)};
		//return new Control[] {createTabbedPane(parent)};
	}

	public void selectionChanged(SelectionChangedEvent event) {
		StructuredSelection selection = (StructuredSelection)event.getSelection();
		JavaElement element = selection.isEmpty() ? null : (JavaElement)selection.getFirstElement();
		if (element != null && !ROOT_PATH.equals(element.getPath())) {
			valueChanged(element.getPath());
		} else {
			valueChanged(EMPTY_PATH);
		}
	}

	public void cut() {
	}

	public void copy() {
	}

	public void paste() {
	}

	public void delete() {
	} 

	private AccessibleJava.IContextPackageProvider getPackageProvider() {
		IModelPropertyEditorAdapter adapter = (IModelPropertyEditorAdapter)propertyEditor.getInput();
		XAttribute a = adapter.getAttribute();
		if(a == null) return null;
		String cls = a.getProperty("contextPackageProvider"); //$NON-NLS-1$
		if(cls == null || cls.length() == 0) return null;
		try {
			AccessibleJava.IContextPackageProvider o = (AccessibleJava.IContextPackageProvider)ModelFeatureFactory.getInstance().createFeatureInstance(cls);
			if(o != null) o.setObject(((DefaultValueAdapter)adapter).getModelObject());
			return o;
		} catch (ClassCastException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}		
		return null;
	}
}
