/*
 * Copyright (c) 2006-2008 Maskat Project.
 *
 * 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:
 *     Maskat Project - initial API and implementation
 */
package org.maskat.core.layout.custom;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.beanutils.BasicDynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.PredicateUtils;

import org.maskat.core.MaskatElement;
import org.maskat.core.MaskatElementVisitor;
import org.maskat.core.event.Event;
import org.maskat.core.event.EventDef;
import org.maskat.core.event.Source;
import org.maskat.core.event.Target;
import org.maskat.core.layout.Container;
import org.maskat.core.layout.Component;
import org.maskat.core.layout.Layout;
import org.maskat.core.layout.LayoutElement;

public class DynaComponent extends BasicDynaBean implements Component {

	private static final long serialVersionUID = 925589796525491481L;

	private PropertyChangeSupport support;

	private MaskatElement parent;
	
	public DynaComponent() {
		super(null);
		this.support = new PropertyChangeSupport(this);
	}
	
	public DynaComponent(DynaClass dynaClass) {
		super(dynaClass);
		this.support = new PropertyChangeSupport(this);
	}
	
	public void setDynaClass(DynaClass dynaClass) {
		this.dynaClass = dynaClass;
	}
    
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.beanutils.BasicDynaBean#set(java.lang.String,
	 *      int, java.lang.Object)
	 */
	public void set(String name, int index, Object value) {
		Object oldValue = get(name, index);
		super.set(name, index, value);
		support.firePropertyChange(name, oldValue, value);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.beanutils.BasicDynaBean#set(java.lang.String,
	 *      java.lang.Object)
	 */
	public void set(String name, Object value) {
		Object oldValue = get(name);
		super.set(name, value);
		support.firePropertyChange(name, oldValue, value);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.beanutils.BasicDynaBean#set(java.lang.String,
	 *      java.lang.String, java.lang.Object)
	 */
	public void set(String name, String key, Object value) {
		Object oldValue = get(key, name);
		super.set(name, key, value);
		support.firePropertyChange(name, oldValue, value);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.beanutils.BasicDynaBean#remove(java.lang.String,
	 *      java.lang.String)
	 */
	public void remove(String name, String key) {
		Object oldValue = get(key, name);
		super.remove(name, key);
		support.firePropertyChange(name, oldValue, null);
	}

	public MaskatElement getParent() {
		return parent;
	}

	public void setParent(MaskatElement parent) {
		this.parent = parent;
	}

	protected List getRawChildren() {
		List result = (List) get("children");
		if (result == null) {
			result = new ArrayList();
			set("children", result);
		}
		return result;
	}

	public Iterator getChildren() {
		return getRawChildren().iterator();
	}

	public Iterator getTypedChildren(Class clazz) {
		return IteratorUtils.filteredIterator(getChildren(), PredicateUtils
				.instanceofPredicate(clazz));
	}

	public void addChild(Object child) {
		getRawChildren().add(child);
		if (child instanceof MaskatElement) {
			((MaskatElement) child).setParent(this);
		}
		support.firePropertyChange("addChild", "", null);
	}

	public void addChildToIdx(Object child, int idx) {
		getRawChildren().add(idx, child);
		if (child instanceof MaskatElement) {
			((MaskatElement) child).setParent(this);
		}
		support.firePropertyChange("addChild", "", null);
	}

	public int getChildIdx(Object child) {
		return getRawChildren().indexOf(child);
	}

	public Object getChildByTypeIdx(Class clazz, int idx) {
		List result = new ArrayList();
		CollectionUtils.select(getRawChildren(), PredicateUtils
				.instanceofPredicate(Component.class), result);
		return result.get(idx);
	}

	public List getUnmodifiableChildren() {
		return Collections.unmodifiableList(getRawChildren());
	}
	
	public void removeAllChildren() {
		getRawChildren().clear();
		support.firePropertyChange("removeChild", "", null);
	}

	public void removeChild(Object obj) {
		getRawChildren().remove(obj);
		support.firePropertyChange("removeChild", "", null);
	}

	public void accept(MaskatElementVisitor visitor) {
		Iterator iter = getChildren();
		while (iter.hasNext()) {
			Object element = iter.next();
			visitor.visit(element);
			if (element instanceof MaskatElement) {
				((MaskatElement) element).accept(visitor);
			}
		}
	}

	public Layout getLayout() {
		return getParent() == null ? null : ((LayoutElement) getParent()).getLayout();
	}

	public String getName() {
		return (String) get("name");
	}

	public void setName(String name) {
		String oldName = (String)get("name");
		set("name", name);
		updateComponentIdInEvent(oldName, name);
	}

	public int getTop() {
		return ((Integer) get("top")).intValue();
	}
	
	public void setTop(int top) {
		set("top", new Integer(top));
	}

	public int getLeft() {
		return ((Integer) get("left")).intValue();
	}
	
	public void setLeft(int left) {
		set("left", new Integer(left));
	}

	public int getHeight() {
		return values.containsKey("height") ?
			((Integer) get("height")).intValue() : 0;
	}

	public void setHeight(int height) {
		set("height", new Integer(height));
	}

	public int getWidth() {
		return values.containsKey("width") ?
			((Integer) get("width")).intValue() : 0;
	}

	public void setWidth(int width) {
		set("width", new Integer(width));
	}

	public void setConstraint(int left, int top, int width, int height) {
		super.set("left", new Integer(left));
		super.set("top", new Integer(top));
		if (getDynaClass().getDynaProperty("width") != null) {
			super.set("width", new Integer(width));
		}
		if (getDynaClass().getDynaProperty("height") != null) {
			super.set("height", new Integer(height));
		}
		support.firePropertyChange("constraint", null, null);
	}

	public int getTabIndex() {
		return ((Integer) get("tabIndex")).intValue();
	}

	public void setTabIndex(int tabIndex) {
		set("tabIndex", new Integer(tabIndex));
	}

	public void addPropertyChangeListener(PropertyChangeListener listener) {
		support.addPropertyChangeListener(listener);
	}

	public void removePropertyChangeListener(PropertyChangeListener listener) {
		support.removePropertyChangeListener(listener);
	}

	public Container getContainer() {
		return (Container) getParent();
	}

	public String[] getEventTypes() {
		return ((DynaComponentClass) getDynaClass()).getEventTypes();
	}

	public boolean hasEvent(String eventType) {
		String[] eventTypes = getEventTypes();
		if (eventTypes != null) {
			for (int i = 0; i < eventTypes.length; i++) {
				if (eventTypes[i].equals(eventType)) {
					return true;
				}
			}
		}
		return false;
	}

	public Event getEvent(String eventType) {
		if (hasEvent(eventType)) {
			EventDef eventDef = getLayout().getLayoutDef().getEventDef();

			org.maskat.core.event.Component component = eventDef.findComponent(getName());
			if (component == null) {
				component = new org.maskat.core.event.Component();
				component.setId(getName());
				eventDef.addChild(component);
			}

			Event event = component.findEvent(eventType);
			if (event == null) {
				event = new Event(eventType);
				component.addChild(event);
			}
			return event;
		}
		return null;
	}
	
	public Object clone() throws CloneNotSupportedException {
		DynaComponent dynaBean = (DynaComponent) super.clone();
		dynaBean.values = (HashMap) dynaBean.values.clone();
		dynaBean.set("children", new ArrayList());
		dynaBean.setParent(null);

		for (Iterator it = this.getChildren(); it != null && it.hasNext();) {
			Object def = it.next();
			if (def instanceof MaskatElement) {
				dynaBean.addChild(((MaskatElement) def).clone());
			} else if (def instanceof DynaComponent) {
				dynaBean.addChild(((DynaComponent) def).clone());
			} else {
				dynaBean.addChild(def);
			}
		}
		dynaBean.support = new PropertyChangeSupport(dynaBean);
		return dynaBean;
	}
	
	public void getAllDescendants(MaskatElement parent, Class descendantClass, ArrayList result) {
		if (parent == null || result == null) {
			return;
		}
		
		Iterator it = parent.getChildren();
		while (it != null && it.hasNext()) {
			Object child = it.next();
			if (!(child instanceof MaskatElement)) {
				continue;
			}
			if (descendantClass.equals(child.getClass())) {
				result.add(child);
			}
			getAllDescendants((MaskatElement)child, descendantClass, result);
		}
	}
	
	public static boolean isAttributeProperty(String propName) {
		return !("children".equals(propName) || "parent".equals(propName));
	}
	
	private void updateComponentIdInEvent(String oldId, String newId) {
		if (oldId == null || oldId.equals(newId)) {
			return;
		}
		if (getParent() == null || getLayout() == null 
				|| getLayout().getLayoutDef() == null
				|| getLayout().getLayoutDef().getEventDef() == null) {
			return;
		}
		
		EventDef eventDef = getLayout().getLayoutDef().getEventDef();
		org.maskat.core.event.Component component = eventDef.findComponent(oldId);
		if (component != null) {
			component.setId(newId);
		}
		
		//TODO should update all Source.obj , Source.idxRef, Target.in, Target.out who equals to oldId ?
		ArrayList sources = new ArrayList();
		getAllDescendants(eventDef, Source.class, sources);
		for (int i=0; i<sources.size(); i++) {
			Source source = (Source)sources.get(i);
			if (oldId.equals(source.getObj())) {
				source.setObj(newId);
			}
			if (oldId.equals(source.getIdxRef())) {
				source.setIdxRef(newId);
			}
		}
		ArrayList targets = new ArrayList();
		getAllDescendants(eventDef, Target.class, targets);
		for (int i=0; i<targets.size(); i++) {
			Target target = (Target)targets.get(i);
			if (oldId.equals(target.getIn())) {
				target.setIn(newId);
			}
			if (oldId.equals(target.getOut())) {
				target.setOut(newId);
			}
		}
	}
}
