/*
 * Copyright 2006 Mask@ Project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.maskat.ide.gef.editparts;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.CompoundBorder;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.LineBorder;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.views.properties.IPropertySource;
import org.maskat.framework.screendef.ButtonDef;
import org.maskat.framework.screendef.CheckboxDef;
import org.maskat.framework.screendef.CodeLibDef;
import org.maskat.framework.screendef.ComboDef;
import org.maskat.framework.screendef.GridDef;
import org.maskat.framework.screendef.IComponentDef;
import org.maskat.framework.screendef.IContainerDef;
import org.maskat.framework.screendef.ImageDef;
import org.maskat.framework.screendef.LabelDef;
import org.maskat.framework.screendef.LayoutDef;
import org.maskat.framework.screendef.RadioDef;
import org.maskat.framework.screendef.SplitterDef;
import org.maskat.framework.screendef.TextDef;
import org.maskat.framework.screendef.TreeViewDef;
import org.maskat.framework.screendef.customize.IPluginComponentDef;
import org.maskat.ide.gef.editpolicies.MaskatComponentEditPolicy;
import org.maskat.ide.gef.editpolicies.MyXYLayoutEditPolicy;
import org.maskat.ide.gef.figure.CodeLibFigure;
import org.maskat.ide.gef.figure.ImageFigure;
import org.maskat.ide.gef.figure.LayoutFigure;
import org.maskat.ide.gef.figure.MaskatGridFigure;
import org.maskat.ide.gef.figure.NullBorder;
import org.maskat.ide.gef.figure.SplitterFigure;

public class ComponentEditPart extends AbstractGraphicalEditPart implements
		PropertyChangeListener {
	static final Image CHECKBOXUNCHECKED = createImage("/images/checkboxenabledoff.gif"), //$NON-NLS-1$
			RADIOUNCHECKED = createImage("/images/radioboxenableoff.gif"), //$NON-NLS-1$
			COMBOBOXDROPDOWN = createImage("/images/comboboxdropdown.gif"); //$NON-NLS-1$

	private static Image createImage(String name) {
		InputStream stream = ComponentEditPart.class.getResourceAsStream(name);
		Image image = new Image(null, stream);
		try {
			stream.close();
		} catch (IOException ioe) {
		}
		return image;
	}

	public static Font DEFAULT_FONT = new Font(null, "Arial", 8, 0);

	public static Font BIG_FONT = new Font(null, "Arial", 12, 0);

	// public static Font FONT_COMP_TYPE = new Font(null, "Arial", 8, SWT.BOLD |
	// SWT.ITALIC);

	public void activate() {
		super.activate();
		IComponentDef model = (IComponentDef) getModel();
		model.addPropertyChangeListener(this);
	}

	public void deactivate() {
		IComponentDef model = (IComponentDef) getModel();
		model.removePropertyChangeListener(this);
		super.deactivate();
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#createFigure()
	 */
	protected IFigure createFigure() {

		IComponentDef model = (IComponentDef) getModel();
		IFigure figure = createFigure(model);
		// figure.addKeyListener(new KeyListener() {
		//
		// public void keyPressed(KeyEvent ke) {
		// if (ke.keycode == SWT.ARROW_LEFT) {
		// ComponentDef model = (ComponentDef) getModel();
		// model.setLeft(model.getLeft() - 1);
		// }
		// }
		//
		// public void keyReleased(KeyEvent ke) {
		// }
		// });
		XYLayout layout = new XYLayout();
		figure.setLayoutManager(layout);
		return figure;
	}

	private IFigure createFigure(IComponentDef model) {
		Label label = null;
		if (model instanceof LabelDef) {
			label = new Label();
			label.setFont(DEFAULT_FONT);
			// 外枠とマージンの設定
			label.setBorder(new NullBorder());
			label.setText(((LabelDef) model).getText());
			// Rectangle dim = label.getTextBounds();
			// model.setWidth(dim.width + 12);
		}
		if (model instanceof TextDef) {
			label = new Label();
			label.setFont(DEFAULT_FONT);
			// 外枠とマージンの設定
			label.setBorder(new CompoundBorder(new LineBorder(), new MarginBorder(1)));
		}
		if (model instanceof ButtonDef) {
			label = new Label();
			label.setFont(DEFAULT_FONT);
			// 外枠とマージンの設定
			label.setBorder(new CompoundBorder(new LineBorder(), new MarginBorder(1)));
			label.setText(((ButtonDef) model).getTitle());
			adjustModelPreferredSize(label, model);
		}
		if (model instanceof CheckboxDef) {
			CheckboxDef cd = (CheckboxDef) model;
			label = new Label(cd.getText(), CHECKBOXUNCHECKED);
			label.setFont(DEFAULT_FONT);
			label.setLabelAlignment(PositionConstants.LEFT);
			adjustModelPreferredSize(label, model);
		}
		if (model instanceof RadioDef) {
			RadioDef rd = (RadioDef) model;
			label = new Label(rd.getText(), RADIOUNCHECKED);
			label.setFont(DEFAULT_FONT);
			label.setLabelAlignment(PositionConstants.LEFT);
			adjustModelPreferredSize(label, model);
		}
		if (model instanceof ComboDef) {
			label = new Label("　　　　", COMBOBOXDROPDOWN);
			label.setFont(DEFAULT_FONT);
			label.setBorder(new CompoundBorder(new LineBorder(), new MarginBorder(0)));
			label.setTextPlacement(PositionConstants.WEST);
			label.setLabelAlignment(PositionConstants.RIGHT);
			// adjustModelPreferredSize(label, model);
		}
		if (model instanceof ImageDef) {
			label = new ImageFigure();
			label.setBorder(new CompoundBorder(new LineBorder(), new MarginBorder(0)));
			adjustModelPreferredSize(label, model);
		}
		if (model instanceof CodeLibDef) {
			label = new CodeLibFigure();
			CodeLibDef codeLib = (CodeLibDef) model;
			model.setHeight(20);
			if (codeLib.hasLabelInput()) {
				((CodeLibFigure) label).setWithLabelInput(true);
			} else {
				((CodeLibFigure) label).setWithLabelInput(false);
			}
		}
		if (model instanceof GridDef) {
			MaskatGridFigure gridFigure = new MaskatGridFigure((GridDef) model);
			gridFigure.setFont(DEFAULT_FONT);
			gridFigure
					.setBorder(new CompoundBorder(new LineBorder(), new MarginBorder(0)));
			return gridFigure;
		}

		if (model instanceof TreeViewDef) {
			label = new Label("Tree View\n" + model.getName());
			label.setBorder(new CompoundBorder(new LineBorder(), new MarginBorder(5)));
			label.setFont(BIG_FONT);

			return label;
		}
		if (model instanceof LayoutDef) {
			LayoutFigure figure = new LayoutFigure();
			return figure;
		}
		if (model instanceof IPluginComponentDef) {
			label = new Label();
			label.setBorder(new LineBorder(1));
			// label.setText(((IPluginComponentDef)
			// model).getProxy().getQName());
			// label.setFont(FONT_COMP_TYPE);
		}
		if (model instanceof IContainerDef && label == null) {
			label = new Label();
			label.setBorder(new LineBorder(1));
		}

		if (label != null) {
			// 背景色を透明に
			label.setOpaque(false);
		}
		if (label == null)
			return new Figure();
		return label;
	}

	/**
	 * レイアウト定義に欠けているサイズ定義に対して、ここでは相応しいサイズを計算(たとえば、<br>
	 * Labelの幅は全部文字できるように調整,Imageのサイズは20X10と固定)
	 * 
	 * @param figure
	 * @param model
	 */
	private void adjustModelPreferredSize(Figure figure, IComponentDef model) {
		if (model instanceof ImageDef) {
			model.setWidth(20);
			model.setHeight(10);
			return;
		}
		// if (model instanceof GridDef) {
		// GridDef gridModel = (GridDef) model;
		// int headerWidth = gridModel.headerTotalWidth();
		// if (headerWidth == 0) {
		// gridModel.setWidth(100);
		// } else {
		// gridModel.setWidth(headerWidth);
		// }
		// return;
		// }
		Dimension dim = figure.getPreferredSize();
		model.setWidth(dim.width);
		model.setHeight(dim.height);
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.gef.editparts.AbstractEditPart#createEditPolicies()
	 */
	protected void createEditPolicies() {
		installEditPolicy(EditPolicy.COMPONENT_ROLE, new MaskatComponentEditPolicy());
		installEditPolicy(EditPolicy.LAYOUT_ROLE, new MyXYLayoutEditPolicy());
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.gef.editparts.AbstractEditPart#getModelChildren()
	 */
	protected List getModelChildren() {
		List result = null;
		if (getModel() instanceof IContainerDef) {
			result = ((IContainerDef) getModel()).getUnmodifiableChildren();
		}
		if (getModel() instanceof GridDef) {
			result = ((GridDef) getModel()).getUnmodifiableChildren();
		}
		if (result != null)
			return result;

		return super.getModelChildren();
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.gef.editparts.AbstractEditPart#refreshVisuals()
	 */
	protected void refreshVisuals() {
		// TODO If the component's width or size is smaller than 0, we should
		// set one "minimal" width or size, so that the component is "visible"
		// on the IDE's GUI.
		// TODO Some components' width or height property is "invalid", which
		// means that even if the size of the component is set, the appearance
		// may differ from the setting. Eg. Grid's width equals to the total
		// width of the GridHeader. Button's height is fixed.

		IComponentDef comp = (IComponentDef) getModel();
		Rectangle constraint;
		if (comp instanceof LabelDef) {
			// 外枠とマージンの設定
			constraint = ((Label) getFigure()).getTextBounds();
			constraint.x = comp.getLeft();
			constraint.y = comp.getTop();
			constraint.height = comp.getHeight();
			if (constraint.width == 0)
				constraint.width = 25;
		} else if (comp instanceof GridDef) {
			int tableWidth = ((GridDef) comp).headerTotalWidth();
			if (tableWidth <= 0)
				tableWidth = 100;// テーブルのデフォルト幅
			constraint = new Rectangle(comp.getLeft(), comp.getTop(), tableWidth, comp
					.getHeight());
		} else {

			// 制約の取得
			constraint = new Rectangle(comp.getLeft(), comp.getTop(), comp.getWidth(),
					comp.getHeight());
		}

		// Rectangleオブジェクトを制約としてビューに設定する
		// setLayoutConstraintメソッドは親EditPartから呼び出す
		((GraphicalEditPart) getParent()).setLayoutConstraint(this, getFigure(),
				constraint);
	}

	public Object getAdapter(Class key) {
		if (IPropertySource.class == key) {
			return null;
			// PropertySheetEntryではこのメソッドを呼び出して、
			// nullを返却、EclipseのPropertySheetビューで表示したくない。
			// MaskatPropertySheetEntryでは特殊処理をして(getPropertySourceを参照)
		}
		return super.getAdapter(key);
	}

	public void propertyChange(PropertyChangeEvent evt) {
		IComponentDef comp = (IComponentDef) getModel();
		if ("text".equals(evt.getPropertyName())) {
			// 表示文字の変更
			if (comp instanceof LabelDef) {
				((Label) getFigure()).setText((String) evt.getNewValue());
			}
			if (comp instanceof RadioDef) {
				((Label) getFigure()).setText((String) evt.getNewValue());
			}
			// 表示文字の変更
			if (comp instanceof GridDef) {
				this.getFigure().repaint();
			}
			if (comp instanceof CheckboxDef) {
				((Label) getFigure()).setText((String) evt.getNewValue());
			}
		}
		if ("title".equals(evt.getPropertyName())) {
			if (comp instanceof ButtonDef) {
				((Label) getFigure()).setText((String) evt.getNewValue());
			}
		}
		if ("orientation".equals(evt.getPropertyName())) {
			if (comp instanceof SplitterDef) {
				((SplitterFigure) getFigure()).repaint();
			}
		}
		if ("gridHeaderTitle".equals(evt.getPropertyName())
				|| "gridHeaderWidth".equals(evt.getPropertyName())
				|| "lineHeight".equals(evt.getPropertyName())) {
			if (comp instanceof GridDef) {
				for (Iterator it = this.getChildren().iterator(); it != null
						&& it.hasNext();) {
					((AbstractGraphicalEditPart) it.next()).refresh();
				}
				this.getFigure().repaint();
			}
		}
		if ("boolWithLabel".equals(evt.getPropertyName())) {
			if (comp instanceof CodeLibDef) {
				((CodeLibFigure) getFigure()).setWithLabelInput(((CodeLibDef) comp)
						.hasLabelInput());
				this.getFigure().repaint();
			}
		}
		if ("addChild".equals(evt.getPropertyName())
				|| "removeChild".equals(evt.getPropertyName())) {
			refreshChildren();
			if (comp instanceof GridDef) {
				for (Iterator it = this.getChildren().iterator(); it != null
						&& it.hasNext();) {
					((AbstractGraphicalEditPart) it.next()).refresh();
				}
				this.getFigure().repaint();
			}
		}
		refreshVisuals();
	}

	/**
	 * Search for editpart(s) belongs to the editPart, whose model have the
	 * specified name. And switch the visibility of the editPart's figure. It
	 * could be choosen whether to switch all found editpart or the first one
	 * found.
	 * 
	 * @param editPart
	 * @param name
	 * @param firstOnly
	 * @return
	 */
	protected boolean switchNamedComponentVisibility(AbstractGraphicalEditPart editPart,
			String name, boolean firstOnly) {
		List children = editPart.getChildren();
		if (children == null)
			return false;

		boolean found = false;
		Iterator it = children.iterator();
		while (it != null && it.hasNext()) {
			AbstractGraphicalEditPart child = (AbstractGraphicalEditPart) it.next();
			Object model = child.getModel();
			if (model instanceof IComponentDef) {
				if (name.equals(((IComponentDef) model).getName())) {
					IFigure figure = child.getFigure();
					if (figure != null) {
						figure.setVisible(!figure.isVisible());
						found = true;
						if (firstOnly)
							return true;
					}
				}
			}
			if (switchNamedComponentVisibility(child, name, firstOnly)) {
				found = true;
			}
			if (found && firstOnly)
				return true;
		}
		return found;
	}
}
