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

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap;

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 org.apache.commons.beanutils.PropertyUtils;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;

/**
 * BeanBlock用のController Object
 * @author iwasaki
 */
public class BeanBlockObject implements IPropertySource, IBlockObject {
	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(BeanBlockObject.class);
	
	private BlockDiagramGraphWidget bdg_;
	private BeanBlock block_;
	private HashMap<String,PropertyInfo> propertyInfos_=new HashMap<String, BeanBlockObject.PropertyInfo>();
	
	class PropertyInfo {
		private String name_;
		private Class<?> valueClass_;
		
		public PropertyInfo(String name, Class<?> valueClass) {
			super();
			name_ = name;
			valueClass_ = valueClass;
		}
		
		public String getName() {
			return name_;
		}
		
		public Class<?> getValueClass() {
			return valueClass_;
		}
		
		public Object getValue() {
			return block_.getProperty(name_);
		}
		
		public void setValue(Object value) {
			block_.setProperty(name_, value);
		}
	}

	public BeanBlockObject(BlockDiagramGraphWidget bdg, BeanBlock block) {
		super();
		bdg_ = bdg;
		block_ = block;
		buildPropertyInfos();
	}
	
	private void buildPropertyInfos() {
		propertyInfos_.clear();
		
		// from block diagram properties
		for(String key: block_.properties().keySet()) {
			Object value=block_.getProperty(key);
			if (value!=null) {
				if (isPropetyValueClassSupported(value.getClass())) {
					PropertyInfo pi = new PropertyInfo(key, value.getClass());
					propertyInfos_.put(key, pi);
				}
			}
		}
		
		// from class reflection
		try{
			Class<?> c = Class.forName(block_.getRuntimeClassName());
			buildPropertyInfosFromClass(c);
		}catch(ClassNotFoundException ex) {
			LOG.info("ClassNotFoundException",ex);
		}
	}

	private void buildPropertyInfosFromClass(Class<?> c) {
		for(PropertyDescriptor pd: PropertyUtils.getPropertyDescriptors(c)) {
			if (pd.getWriteMethod()!=null) {
				String key=pd.getName();
				Class<?> vt = covertPrimitiveToClass(pd.getPropertyType());
				//LOG.debug(key+":"+vt.getName());
				if (isPropetyValueClassSupported(vt)) {
					PropertyInfo pi = new PropertyInfo(key, vt);
					propertyInfos_.put(key, pi);
				}
			}
		}
		Class<?> sc = c.getSuperclass();
		if (sc!=null && !sc.equals(Object.class)) {
			buildPropertyInfosFromClass(sc);
		}
	}

	public static Class<?> covertPrimitiveToClass(Class<?> propertyType) {
		if (propertyType==null || !propertyType.isPrimitive())
			return propertyType;
		if (propertyType==Boolean.TYPE) return Boolean.class;
		if (propertyType==Character.TYPE) return Character.class;
		if (propertyType==Byte.TYPE) return Byte.class;
		if (propertyType==Short.TYPE) return Short.class;
		if (propertyType==Integer.TYPE) return Integer.class;
		if (propertyType==Long.TYPE) return Long.class;
		if (propertyType==Float.TYPE) return Float.class;
		if (propertyType==Double.TYPE) return Double.class;
		if (propertyType==Void.TYPE) return Void.class;
		throw new RuntimeException("Unknown class: "+propertyType.getName());
	}

	@Override
	public AbstractBlock getBlock() {
		return block_;
	}

	public BeanBlock getBeanBlock() {
		return block_;
	}
	
	private static final String CAT_TYPE = "_block";
	private static final String CAT_PROPERTIES = "bean properties";

	private final static String P_NAME = "P_NAME";
	private final static String P_TYPE = "P_TYPE";
	private final static String P_CLASS = "P_CLASS";
	private final static String P_ENABLED = "P_ENABLED";
	private final static String P_COMMENT = "P_COMMENT";
	private final static String P_PROPERTIES_PRIFIX = "P_PROPERTIES_";
	
	
	@Override
	public IPropertyDescriptor[] getPropertyDescriptors() {
		ArrayList<IPropertyDescriptor> descriptors=new ArrayList<IPropertyDescriptor>();
		descriptors.add(new TextPropertyDescriptor(P_TYPE,"_Type"));
		descriptors.add(new TextPropertyDescriptor(P_NAME,"Name"));
		descriptors.add(new TextPropertyDescriptor(P_CLASS,"Class"));
		descriptors.add(new TextPropertyDescriptor(P_ENABLED,"Enabled"));
		descriptors.add(new TextPropertyDescriptor(P_COMMENT,"Comment"));
		for(IPropertyDescriptor pd : descriptors) { ((TextPropertyDescriptor)pd).setCategory(CAT_TYPE); }
		for(PropertyInfo pi: propertyInfos_.values()) {
			TextPropertyDescriptor pd = new TextPropertyDescriptor(P_PROPERTIES_PRIFIX+pi.getName(),pi.getName());
			pd.setCategory(CAT_PROPERTIES);
			descriptors.add(pd);
		}
		
		return descriptors.toArray(new IPropertyDescriptor[descriptors.size()]);
	}
	
	private String getPropertyKey(Object id) {
		if (id instanceof String) {
			String idStr = (String) id;
			if (idStr.startsWith(P_PROPERTIES_PRIFIX) && block_.properties()!=null) {
				String key=idStr.substring(P_PROPERTIES_PRIFIX.length());
				return key;
			}
		}
		return null;
	}


	@Override
	public Object getPropertyValue(Object id) {
		if(P_TYPE.equals(id)){
			return "BeanBlock";
		}else if(P_NAME.equals(id)){
			return block_.getName();
		}else if(P_CLASS.equals(id)){
			return block_.getRuntimeClassName();
		}else if(P_ENABLED.equals(id)){
			return block_.isEnabled()?"true":"false";
		}else if(P_COMMENT.equals(id)){
			return block_.getComment()!=null?block_.getComment():"";
		}else{
			String key=getPropertyKey(id);
			if (key!=null) {
				PropertyInfo pi = propertyInfos_.get(key);
				return propertyValueToString(pi.getValue());
			}
		}
		return null;
	}

	@Override
	public Object getEditableValue() {
		return null;
	}

	@Override
	public boolean isPropertySet(Object id) {
		String key=getPropertyKey(id);
		if (key!=null) {
			PropertyInfo pi = propertyInfos_.get(key);
			return pi.getValue()!=null;
		}
		return false;
	}


	@Override
	public void resetPropertyValue(Object id) {
		String key=getPropertyKey(id);
		if (key!=null) {
			PropertyInfo pi = propertyInfos_.get(key);
			pi.setValue(null);
			bdg_.updateText();
		}
	}


	@Override
	public void setPropertyValue(Object id, Object value) {
//		if(P_NAME.equals(id)){
//			setName((String)value); // is it possible?
//		}
		LOG.debug("setPropertyValue: "+id+"="+value);
		
		String valueStr=value!=null?value.toString():"";
		if(P_COMMENT.equals(id)){
			block_.setComment(valueStr.isEmpty()?null:valueStr);
			bdg_.updateText();
		} else if(P_ENABLED.equals(id)){
			block_.setEnabled(valueStr.startsWith("t") ||
					valueStr.startsWith("T") ||
					valueStr.equals("1"));
		}else if(P_NAME.equals(id)){
			if (!valueStr.isEmpty()) {
				// refactor
				String oldName=block_.getName();
				String newName=valueStr;
				BlockDiagram bd = bdg_.getBlockDiagram();
				for(AbstractBlock blk:bd.getBlocks()) {
					if (blk.getName().equals(newName)) {
						LOG.info("block name duplicated: "+newName);
						return;
					}
				}
				
				//
				block_.setName(newName);
				for(Connection c:bd.getConnections()) {
					if (c.getInputPortReference().getBlockName().equals(oldName)) {
						c.setInputPortReference(
							block_.getPortByName(c.getInputPortReference().getPortName())
						);
					}
					if (c.getOutputPortReference().getBlockName().equals(oldName)) {
						c.setOutputPortReference(
							block_.getPortByName(c.getOutputPortReference().getPortName())
						);
					}
				}
				//
				bdg_.updateText();
				bdg_.updateDiagram();
			}
		}else if(P_CLASS.equals(id)){
			try{
				Class.forName(valueStr);
				block_.setRuntimeClassName(valueStr);
				buildPropertyInfos();
			}catch(ClassNotFoundException ex) {
				LOG.info("ClassNotFoundException",ex);
				return;
			}
			bdg_.updateText();
		}else{
			String key=getPropertyKey(id);
			if (key!=null) {
				PropertyInfo pi = propertyInfos_.get(key);
				try{
					Class<?> c = pi.getValueClass();
					Object v = stringToPropertyValue(valueStr, c);
					pi.setValue(v);
				}catch(Exception ex) {
					LOG.info("Exception",ex);
					return;
				}
			}else{
				LOG.warn("Unknown property id:"+id);
			}
			bdg_.updateText();
		}
	}
	
	private boolean isPropetyValueClassSupported(Class<?> c) {
		return c.equals(String.class) || c.equals(Integer.class) || c.equals(Long.class) || c.equals(Float.class) || c.equals(Double.class)
			|| c.equals(Boolean.class) || c.equals(Character.class) || c.equals(Byte.class) || c.equals(Short.class);
	}
	
	private String propertyValueToString(Object value) {
		return value==null?"":value.toString();
	}

	private Object stringToPropertyValue(String valueStr, Class<?> c) throws Exception {
		if (valueStr.isEmpty()) {
			return null;
		} else if (c.equals(String.class)) {
			return valueStr;
		}else if (c.equals(Integer.class)) {
			return Integer.parseInt(valueStr);
		}else if (c.equals(Long.class)) {
			return Long.parseLong(valueStr);
		}else if (c.equals(Float.class)) {
			return Float.parseFloat(valueStr);
		}else if (c.equals(Double.class)) {
			return Double.parseDouble(valueStr);
		}else if (c.equals(Boolean.class)) {
			return Boolean.parseBoolean(valueStr);
		}else if (c.equals(Character.class)) {
			return new Character((valueStr.length()>0)?valueStr.charAt(0):'\0');
		}else if (c.equals(Byte.class)) {
			return Byte.parseByte(valueStr);
		}else if (c.equals(Short.class)) {
			return Short.parseShort(valueStr);
		}else throw new RuntimeException("Value class not supported: "+c.getName());
	}
}
