package com.eactive.eai.transformer.message;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.PropertyUtils;

import com.eactive.eai.common.util.ObjectUtil;
import com.eactive.eai.transformer.layout.Item;
import com.eactive.eai.transformer.layout.Layout;
import com.eactive.eai.transformer.layout.Types;


public class ObjectMessage extends Message {
    public static final String NAME = "OBJECT";
    private Object data;

    //-------------------------------------------------
    // 2007.02.16 : IBM BMT Version : 2007.05.09 적용
    //-------------------------------------------------
    public static final Pattern pattern = Pattern.compile("([a-zA-Z0-9_]+)\\[[a-zA-Z0-9_]+\\]");
    //-------------------------------------------------
    
    //private Logger logger;
    public ObjectMessage() {
        super();
    }

    /*
    public ObjectMessage(Layout layout) {
        this.layout = layout;
    }
    */
    public Object getData() {
        return this.data;
    }

    public void createObject() {
        //Item root = layout.getGroup(0).getRootItem();
        String className = getRefInfo();

        try {
            this.data = this.newInstance(className);
        } catch (Exception e) {
            throw new RuntimeException(
                "ObjectMessage] Cannot create a ObjectMessage object. - " +
                e.getMessage());
        }
    }
    protected void doSetData(Object data ) {
    	doSetData(data, false);
    }
    protected void doSetData(Object data , boolean refCountLimit) {
        if (!(data instanceof Serializable)) {
            throw new InvalidDataTypeException("It is not a kind of Serializable class.",
                data);
        }

//        try {
            this.data = data;
//        } catch (Exception e) {
//            throw new InvalidDataTypeException(e.getMessage(), data, e);
//        }

        try {
            Map map = PropertyUtils.describe(data);
            for (Iterator it = map.keySet().iterator(); it.hasNext();) {
                String key = (String) it.next();
                Object val = map.get(key);

                if (key.equals("class")) {
                    continue;
                }

                Item item = getOrCreateItem(getName() + Types.FIELD_SEPARATOR + key, true);
                item.setValue(val);
                composeData(item, val);

                if ("java.lang.Object".equals(PropertyUtils.getPropertyType(
                                data, key).getName())) {
                    Map _map = PropertyUtils.describe(val);

                    for (Iterator _it = _map.keySet().iterator();
                            _it.hasNext();) {
                        String _key = (String) _it.next();
                        Object _val = _map.get(_key);

                        if (_key.equals("class")) {
                            continue;
                        }

                        Item subItem = getOrCreateItem(getName() +
                                Types.FIELD_SEPARATOR + key + Types.FIELD_SEPARATOR + _key, true);
                        subItem.setValue(_val);
                        composeData(subItem, _val);
                    }
                }
            }
        } catch (Exception e) {
            throw new InvalidAccessException(e.getMessage(), path, this, e);
        }
    }

    protected void composeData(Item item, Object value) throws InvalidAccessException {
        //if(logger.isInfoEnabled()) logger.info("[ObjectMessage " + getItemPath() + "] composeData(" + itemPath + ", " + value + ")");

        if (item.getIndex() < 0)
            return;

        String itemPath = item.getItemPath();
        Item parent = item.getParent();

        if (value != null && value instanceof byte[])
            value = new String((byte[]) value);

        if (parent == null) {
            throw new InvalidAccessException("MESSAGE not defined in top root.",
                itemPath);
        }

        Object target = this.data;

        /**
         * if item is group
         */
        if (item.isGroup() && (value == null)) {
            try {
                item.setValue(newInstance(item.getRefInfo()));

                // Message 밑에만 Group이 있다 (제한사항) 그룹내 그룹 안된다.
                PropertyUtils.setProperty(this.data, item.getName(),
                    item.getValue());
            } catch (Exception e) {
            	logger.error(e.getMessage(),e);
                throw new InvalidAccessException(
                    "Can not set the default value of the group : can not create an instance of the following class : " + getRefInfo(), 
                    itemPath, 
                    (Message) item.getRoot(),
                    e);
            }
        }

        /**
         * if parent is group
         */
        if (parent.isGroup()) {
            if (parent.getValue() == null) {
                try {
                    parent.setValue(newInstance(item.getParent().getRefInfo()));

                    //PropertyUtils.setProperty(this.data, parent.getName(), parent.getValue()); 
                } catch (Exception e) {
                	logger.error(e.getMessage(),e);
                    throw new InvalidAccessException("Can not set the default value of the group.",
                        itemPath, 
                        (Message) item.getRoot(),
                        e);
                }
            }

            // 최후의 수단이다 - ^^;
            // ObjectMessage -> Nested 인 경우 data에 반영 안되는 현상 제거.
            try {
                PropertyUtils.setProperty(this.data, parent.getName(), parent.getValue());
            } catch (Exception e) {
            	logger.error(e.getMessage(),e);
                throw new InvalidAccessException("Can not set the default value of the group.",
                    itemPath, 
                    (Message) item.getRoot(),
                    e);
            }

            target = item.getParent().getValue();
        }

        try {
            if (target == null) {
                throw new Exception(
                    "Target Item value is null. PropertyUtils.setProperty() can not execute.");
            }

            if (value != null) {
            	/**
            	 * occ type = '*' | '+' | >1
            	 */
                if ("*".equals(item.getOccType()) ||
                        "+".equals(item.getOccType()) || Integer.parseInt(item.getOccType()) > 1) {
                    PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(target,
                            item.getName());

                    if (descriptor == null) {
                        throw new InvalidAccessException(
                            "[ObjectMessage, PropertyDescriptor] Unknown property '" +
                            item.getName() + "'", itemPath);
                    }

                    if (PropertyUtils.getProperty(target, item.getName()) == null) {
                        Object val = Class.forName(PropertyUtils.getPropertyType(
                                    target, item.getName()).getName())
                                          .newInstance();
                        PropertyUtils.setProperty(target, item.getName(), val);
                    }

                    AbstractList prop = (AbstractList) PropertyUtils.getProperty(target,
                            item.getName());

                    // expand size of collection
                    while (prop.size() < (item.getIndex() + 1)) {
                        prop.add(prop.size(),
                            Class.forName(value.getClass().getName())
                                 .newInstance());
                    }

                    PropertyUtils.setIndexedProperty(target, item.getName(),
                        item.getIndex(), value);
                /**
                 * occ type = 1
                 */
                } else {   	
                    PropertyUtils.setProperty(target, item.getName(), value);
                }
            }
        } catch (Exception e) {
//            if(logger.isError()) 
//            	logger.error(e.getMessage() + " : PropertyUtils.setProperty(" +
//                target + ", " + item.getName() + ", " + item.getIndex() + ", " +
//                value + ")", e);
            throw new InvalidAccessException(
                "Can not set the Property to ItemPath, "
            		+ "target=" + target.getClass().getName() + ", "
            		+ e.getMessage(),
                itemPath, 
                (Message) item.getRoot(),
                e);
        }
    }

    public void copy(Message message) {
        if (!(message instanceof ObjectMessage)) {
            throw new InvalidMessageException("ObjectMessage | Invlid Message for copy.",
                message);
        }

        Object obj = null;

        try {
            obj = message.getData();
        } catch (InvalidDataTypeException e) {
            throw new InvalidMessageException(e, message);
        }

        if (!(obj instanceof java.io.Serializable)) {
            throw new InvalidMessageException("ObjectMessage] Invlid Message for deep copy.",
                message);
        }

        try {
            data = ObjectUtil.deepCopy(obj);
        } catch (IOException e) {
            throw new InvalidMessageException(e, message);
        }
    }

    public Layout getLayout() {
        return this.layout;
    }

    public void createObject(Item item) {
        if (item.isField() || item.isMessage()) {
            return;
        }

        Item refItem = item.getReferenceItem();

        if (refItem == null) {
            return;
        }

        Object refItemDefaultValue = refItem.getDefaultValue();

        if (refItemDefaultValue == null) {
            return;
        }

        String className = item.getRefInfo();
        String itemName = item.getName();

        int idx = itemName.indexOf(refItemDefaultValue.toString());

        if (idx == -1) {
            return;
        }

        try {
            Object obj = this.newInstance(className);
            this.setObject(item.getPath(), obj);
        } catch (Exception e) {
            throw new RuntimeException(
                "[ObjectMessage] Cannot create a ObjectMessage object. - " +
                e.getMessage());
        }
    }

    private Object getValue(String itemPath) {
        if (this.data == null) {
            this.createObject();
        }

        Item item = getItem(itemPath, true);

        if (item == null) {
            throw new InvalidAccessException("Property not found in ItemPath.",
                itemPath);
        }

        Item parent = item.getParent();

        if (parent == null) {
            throw new InvalidAccessException("MESSAGE not defined in top root.",
                itemPath);
        }

        String _path = pattern.matcher(item.getName()).replaceAll("$1");
        
        Object target = this.data;

        if (item.getParent().isGroup()) {
            if (item.getParent().getValue() == null) {
                try {
                    parent.setValue(newInstance(item.getRefInfo()));
                } catch (Exception e) {
                    throw new InvalidAccessException(
                        "Can not set the default value of the group : can not create an instance of the following class : " +
                        getRefInfo(), itemPath);
                }
            }

            target = parent.getValue();
        }

        if (parent.isMessage()) {
            try {
                return PropertyUtils.getProperty(target, _path);
            } catch (Exception e) {
            	logger.error(e.getMessage(),e);
                throw new InvalidAccessException("Can not get Property with ItemPath.",
                    itemPath, 
                    (Message) item.getRoot(),
                    e);
            }
        } else if (parent.isGroup()) {
            Object obj = getValue(parent.getItemPath());

            if (obj == null) {
                return null;
            }

            String realPath = item.getName();

            // FIELD
            if (item.isField()) {
                try {
                    return PropertyUtils.getProperty(obj, realPath);
                } catch (Exception e) {
                	logger.error(e.getMessage(),e);
                    throw new InvalidAccessException("Can not get Property with ItemPath.",
                        itemPath, 
                        (Message) item.getRoot(),
                        e);
                }
            } else {
                return getGroupValue(item, itemPath);
            }
        } else {
            throw new InvalidAccessException("Parent can not be defined as FIELD in ItemPath.",
                itemPath);
        }
    }

    private Object getGroupValue(Item item, String itemPath) {
        Item refItem = item.getReferenceItem();

        if (refItem == null) {
            try {
                return PropertyUtils.getProperty(this.data, itemPath);
            } catch (Exception e) {
                throw new InvalidAccessException("Can not get Property with ItemPath.",
                    itemPath, 
                    (Message) item.getRoot(),
                    e);
            }
        } else {
            Object refValue = getObject(refItem.getPath());
            int idx = itemPath.indexOf("[" + refValue + "]");

            if (idx != -1) {
                String reaultPath = itemPath.substring(0, idx);

                try {
                    return PropertyUtils.getProperty(this.data, reaultPath);
                } catch (Exception e) {
                    throw new InvalidAccessException("Can not get Property with ItemPath.",
                        itemPath, 
                        (Message) item.getRoot(),
                        e);
                }
            } else {
                return null;
            }
        }
    }

    private Object newInstance(String className) {
        Class cobj = null;

        try {
            cobj = Class.forName(className);
        } catch (Exception e) {
            throw new RuntimeException(
                "Cannot find the class for value object. [" + className +
                "]- " + e.getMessage());
        }

        try {
            return cobj.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(
                "Cannot create a ObjectMessage object. - " + e.getMessage());
        }
    }

    public String toLogString() {
		StringBuffer sb = new StringBuffer();
		Stack stack = new Stack();
		sb.append(getName() + "[");
		stack.push("]");

		int prevDepth = 1;
		for (Iterator iter = iterator(); iter.hasNext();) {
			Item item = (Item) iter.next();

			/**
			 * index == -1 인 경우는 출력하지 않는다.
			 */
			if (item.getIndex() < 0) {
				continue;
			}
			
			/**
			 * depth 가 낮아질 때(tree를 빠져 나올 때), ']' 를 찍어준다.
			 */
			if (item.getDepth() < prevDepth) {
				sb.append(stack.pop());
			}
			
			/**
			 * ','로 구성요소를 구분해 준다.
			 */
			if (item.getDepth() <= prevDepth) {
				sb.append(", ");
			}

	        switch (item.getNodeTypeId()) {
			case NODE_FIELD:
	            if ("Y".equals(item.getMask()))
	                sb.append(item.getName() + "=" + item.maskString2(item.getValue(), item.getMaskLength()));
	            else
	                sb.append(item.getName() + "=" + item.getValue());
	            break;
			case NODE_GROUP:
			case NODE_MESSAGE:				
				sb.append(item.getName() + "[");
				stack.push("]");
				break;
	        }
	        
	        /**
	         * 이전 depth를 저장해 둔다.
	         */
			prevDepth = item.getDepth();
		}
		
		/**
		 * 필요한 만큼 ']'를 찍어준다.
		 */
		while(stack.size() > 0) {
			sb.append(stack.pop());
		}
		
        return sb.toString();
    }
    
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[OBJECT message]");

        if (data != null) {
            try {
                sb.append("\n    data = |" + PropertyUtils.describe(data) +
                    "|");
            } catch (Exception e) {
            }
        }
        
        sb.append("\n" + super.toString());
        sb.append("\n");

        return sb.toString();
    }
}
