package jp.hasc.hasctool.ui.views.grapheditor;

import java.beans.PropertyDescriptor;
import java.util.Iterator;

import jp.hasc.hasctool.core.blockdiagram.model.AbstractBlock;
import jp.hasc.hasctool.core.blockdiagram.model.BeanBlock;
import jp.hasc.hasctool.core.blockdiagram.model.BlockDiagram;
import jp.hasc.hasctool.core.blockdiagram.model.Connection;
import jp.hasc.hasctool.core.blockdiagram.model.PortReference;
import jp.hasc.hasctool.core.messaging.MessageConnector;
import jp.hasc.hasctool.core.messaging.MessageProcessor;
import jp.hasc.hasctool.ui.util.UIUtil;

import org.apache.commons.beanutils.PropertyUtils;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.views.properties.PropertySheet;
import org.eclipse.zest.core.viewers.GraphViewer;
import org.eclipse.zest.core.widgets.ZestStyles;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.HorizontalTreeLayoutAlgorithm;

/* Implements BlockDiagram <-> Zest Interface */

/**
 * hascxbdエディタのグラフ表示ページ
 */
public class BlockDiagramGraphWidget extends Composite {
	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(BlockDiagramGraphWidget.class);
	
	private GraphViewer graphView_;
//		Graph myGraph;
	private BlockDiagram blockDiagram_;
	private BlockDiagramContentProvider contentProvider_;
	private BlockDiagramLabelProvider labelProvider_;
	//HashMap <AbstractBlock,GraphNode> blockMap_;
	//HashMap <String, AbstractBlock> blockNames_;
	//HashMap <String, GraphConnection> connectionMap_;
	private BlockDiagramEditor editor_;
	private ToolBar toolBar_;
	private String prevInputClassName_=""; //$NON-NLS-1$

	public BlockDiagramGraphWidget(BlockDiagramEditor editor, Composite parent, int style) {
		super(parent,style);
		this.setLayout(new FormLayout());
		
		//
		toolBar_ = new ToolBar(this, SWT.VERTICAL);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(0, 1);
			fd.right=new FormAttachment(100, -1);
			fd.bottom=new FormAttachment(100, -1);
			toolBar_.setLayoutData(fd);
		}
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("+B"); //$NON-NLS-1$
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					InputDialog dlg = new InputDialog(getShell(),
							Messages.BlockDiagramGraphWidget_NewBlock,Messages.BlockDiagramGraphWidget_NewBlockMsg,
							prevInputClassName_, null);
					if (dlg.open() != Dialog.OK)
						return;
					String clsName=dlg.getValue();
					
					addBlock(clsName);
					
					//
					prevInputClassName_=clsName;
				}
			});
		}
		{
			final ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			final String label0="+C"; //$NON-NLS-1$
			ti.setText(label0);
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					IStructuredSelection sel = (IStructuredSelection)graphView_.getSelection();
					Object[] objs = sel.toArray();
					if (objs.length==2 && objs[0] instanceof IBlockObject && objs[1] instanceof IBlockObject) {
						IBlockObject blk0=(IBlockObject)objs[0];
						IBlockObject blk1=(IBlockObject)objs[1];
						LOG.debug(blk0.getBlock().getName()+" to "+blk1.getBlock().getName()); //$NON-NLS-1$
						String pname0="outputPort"; //$NON-NLS-1$
						String pname1="inputPort"; //$NON-NLS-1$
						StringBuilder msgs=new StringBuilder();
						try{
							Class<?> cls0=Class.forName(((BeanBlock)(blk0.getBlock())).getRuntimeClassName());
							PropertyDescriptor desc0 = PropertyUtils.getPropertyDescriptor(cls0.newInstance(), pname0);
							if (desc0==null || desc0.getPropertyType()==null || !MessageConnector.class.isAssignableFrom(desc0.getPropertyType())) {
								pname0=""; //$NON-NLS-1$
								msgs.append(String.format(Messages.BlockDiagramGraphWidget_NoDefaultOutputPort,
										blk0.getBlock().getName()));
								msgs.append("\n"); //$NON-NLS-1$
							}
							
							
							Class<?> cls1=Class.forName(((BeanBlock)(blk1.getBlock())).getRuntimeClassName());
							PropertyDescriptor desc1 = PropertyUtils.getPropertyDescriptor(cls1.newInstance(), pname1);
							if (desc1==null || desc1.getPropertyType()==null || !MessageProcessor.class.isAssignableFrom(desc1.getPropertyType())) {
								pname1=""; //$NON-NLS-1$
								msgs.append(String.format(Messages.BlockDiagramGraphWidget_NoDefaultInputPort,
										blk1.getBlock().getName()));
								msgs.append("\n"); //$NON-NLS-1$
							}
							/*
							if (AbstractMultipleInputsFilter.class.isAssignableFrom(cls1)) {
								// 複数入力
							}else{
								// 単一入力
							}
							*/
							
						}catch(Exception ex) {
							msgs.append(ex.toString());
							msgs.append("\n"); //$NON-NLS-1$
						}
						
						PortReference pr0 = blk0.getBlock().getPortByName(pname0);
						PortReference pr1 = blk1.getBlock().getPortByName(pname1);
						
						// 同一ポートへの複数入力時に警告
						for(Connection c :blockDiagram_.getConnections()) {
							if (c.getInputPortReference().equals(pr1)) {
								msgs.append(String.format(Messages.BlockDiagramGraphWidget_DuplicatedInputPortConnections,
										pr1.getBlockName(), pr1.getPortName()));
								msgs.append("\n"); //$NON-NLS-1$
								break;
							}
						}
						
						// add a connection
						Connection c=new Connection();
						c.setOutputPortReference(pr0);
						c.setInputPortReference(pr1);
						blockDiagram_.addConnection(c);
						editor_.updateText();
						updateDiagram();
						
						// select the connection object
						for(Object co : graphView_.getConnectionElements()) {
							if ((co instanceof ConnectionObject) && ((ConnectionObject)co).getConnection()==c) {
								selectObjects(co);
								break;
							}
						}
						
						// show message
						if (msgs.length()!=0) {
							LOG.info(msgs);
							UIUtil.showMessageDialog(getShell(), msgs.toString());
						}
						
					}else{
						UIUtil.showMessageDialog(getShell(), 
								Messages.BlockDiagramGraphWidget_SelectTwoBlocksToConnect);
					}
				}
			});
		}
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("Del"); //$NON-NLS-1$
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					IStructuredSelection sel = (IStructuredSelection)graphView_.getSelection();
					for(Object obj: sel.toArray()) {
						if (obj instanceof IBlockObject) {
							remove((IBlockObject)obj);
						}else if (obj instanceof ConnectionObject) {
							remove((ConnectionObject)obj);
						}
					}
					editor_.updateText();
					updateDiagram();
				}
			});
		}		
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("Inf"); //$NON-NLS-1$
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					InputDialog dlg = new InputDialog(getShell(),
							"Edit BD Information", "Comment",
							blockDiagram_.getComment(), null);
					if (dlg.open() != Dialog.OK)
						return;
					blockDiagram_.setComment(dlg.getValue());
					//
					editor_.updateText();
				}
			});
		}		
		
		//
		editor_=editor;
		
		//graphView_ = new GraphViewer(this,SWT.NONE);
		graphView_ = new PatchedGraphViewer(this,SWT.NONE);
		graphView_.setNodeStyle(ZestStyles.NODES_NO_LAYOUT_ANIMATION);
		graphView_.setConnectionStyle(ZestStyles.CONNECTIONS_DIRECTED | ZestStyles.CONNECTIONS_SOLID);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(0, 1);
			fd.left=new FormAttachment(0, 1);
			fd.bottom=new FormAttachment(100, -1);
			fd.right=new FormAttachment(toolBar_, -1);
			graphView_.getGraphControl().setLayoutData(fd);
		}
		//graphView.getGraphControl().forceFocus();
				
		//blockMap_ = new HashMap<AbstractBlock,GraphNode>();
		//blockNames_ = new HashMap<String,AbstractBlock>();
		//connectionMap_ = new HashMap<String,GraphConnection>();
	}
	
/*		private GraphNode getGraphNode(PortReference pr) {
			AbstractBlock b = blockNames_.get(pr.getBlockName());
			GraphNode gn = blockMap_.get(b);
			return gn;
		}
	*/	
	public void setBlockDiagram(BlockDiagram bd){
		blockDiagram_ = bd;
		buildDiagram();
	}
	
	private void buildDiagram() {
		contentProvider_ = new BlockDiagramContentProvider(this/*bd_*/);
		labelProvider_ = new BlockDiagramLabelProvider();
		graphView_.refresh();
		graphView_.setContentProvider(contentProvider_);
		graphView_.setLabelProvider(labelProvider_);
		graphView_.setInput(blockDiagram_);
		graphView_.setLayoutAlgorithm(new HorizontalTreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING), false);
		//graphView.addSelectionChangedListener(contentProvider_.selListener_);
	}
	
	public void updateDiagram() {
		graphView_.refresh();
		graphView_.applyLayout();
	}
	
	public BlockDiagram getBlockDiagram() {
		return blockDiagram_;
	}

	public void updateText() {
		editor_.updateText();
	}

	public BlockDiagramContentProvider getContentProvider() {
		return contentProvider_;
	}

	public GraphViewer getGraphView() {
		return graphView_;
	}

	private void remove(IBlockObject obj) {
		for(Iterator<AbstractBlock> it = blockDiagram_.getBlocks().iterator();it.hasNext();) {
			AbstractBlock iobj = it.next();
			if (iobj==obj.getBlock()) it.remove();
		}
		String blkName = obj.getBlock().getName();
		for(Iterator<Connection> it = blockDiagram_.getConnections().iterator();it.hasNext();) {
			Connection iobj = it.next();
			if (iobj.getInputPortReference().getBlockName().equals(blkName)) it.remove();
			else if (iobj.getOutputPortReference().getBlockName().equals(blkName)) it.remove();
		}
	}

	private void remove(ConnectionObject obj) {
		for(Iterator<Connection> it = blockDiagram_.getConnections().iterator();it.hasNext();) {
			Connection iobj = it.next();
			if (iobj==obj.getConnection()) it.remove();
		}
	}
	
	private void selectObjects(Object... objs) {
		StructuredSelection sel = new StructuredSelection(objs);
		graphView_.setSelection(sel, true);
		
		// プロパティビューを更新
		PropertySheet ps;
		try {
			ps = (PropertySheet) editor_.getSite().getWorkbenchWindow().getActivePage().showView(IPageLayout.ID_PROP_SHEET
					,null, IWorkbenchPage.VIEW_CREATE);
			if (ps!=null) {
				ps.selectionChanged(editor_, sel);
			}
		} catch (Exception ex) {
			//ex.printStackTrace();
			LOG.warn("Exception",ex); //$NON-NLS-1$
		}
	}

	public void addBlock(String clsName) {
		// create a class object
		Class<?> cls;
		try{
			cls=Class.forName(clsName);
		}catch(ClassNotFoundException ex) {
			UIUtil.showMessageDialog(getShell(), ex.toString());
			return;
		}
		
		if (!editor_.isGraphPageActive()) {
			editor_.setGraph();
		}
		
		// generate a name
		String name=""; //$NON-NLS-1$
		for(int i=1;i<1000;++i) {
			name=cls.getSimpleName()+i;
			if (!contentProvider_.getBlockObjMap().containsKey(name)) break;
		}
		
		// generate a block
		BeanBlock bb=new BeanBlock();
		bb.setName(name);
		bb.setRuntimeClassName(clsName);
		blockDiagram_.addBlock(bb);
		editor_.updateText();
		updateDiagram();
		
		// select the block object
		IBlockObject bbo = contentProvider_.getBlockObjMap().get(name);
		if (bbo!=null) {
			selectObjects(bbo);
		}
	}
		
/*		
 * modelled by myself;  -> move to JFace
		public void clearCurrentGraph(){
			for(GraphConnection cn : connectionMap_.values()){
				cn.dispose();
			}
			for(GraphNode gn : blockMap_.values()){
				gn.dispose();
			}
			blockMap_ = new HashMap<AbstractBlock,GraphNode>();
			blockNames_ = new HashMap<String,AbstractBlock>();
			connectionMap_ = new HashMap<String,GraphConnection>();
			
		}

		
		public void setBlockDiagram(BlockDiagram bd){
			clearCurrentGraph();
			bd_ = bd;
			for(AbstractBlock b : bd.getBlocks()) {
//				myGraph.
				blockMap_.put(b,new GraphNode(myGraph, SWT.NONE, b.getName()));
				blockNames_.put(b.getName(), b);
			}
			for (Connection c : bd.getConnections()) {
				GraphNode from = getGraphNode(c.getOutputPortReference());
				GraphNode to = getGraphNode(c.getInputPortReference());
				if(from == null ){ //found error on connection
					//LOG.warn("Can't find ", ex);
					LOG.warn("Can't find outputPort Name of Connection "+c.getOutputPortReference().getBlockName());
				}else if(to == null){
					LOG.warn("Can't find inputPort Name of Connection "+c.getInputPortReference().getBlockName());
				}else{
					connectionMap_.put(c.getName(),new GraphConnection(myGraph, SWT.NONE, from, to));
				}
				
			}
/			myGraph.setLayoutAlgorithm(new SpringLayoutAlgorithm(
//					LayoutStyles.NO_LAYOUT_NODE_RESIZING), true);

//			myGraph.setLayoutAlgorithm(new TreeLayoutAlgorithm(
//					LayoutStyles.NO_LAYOUT_NODE_RESIZING), true);
			myGraph.setLayoutAlgorithm(new HorizontalTreeLayoutAlgorithm(
					LayoutStyles.NO_LAYOUT_NODE_RESIZING), true);

		}
*/
}

