package com.eactive.eai.transformer.layout;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.eactive.eai.transformer.message.BytesMessage;
import com.eactive.eai.transformer.message.EbcdicMessage;
import com.eactive.eai.transformer.message.Message;
import com.eactive.eai.transformer.message.MessageException;
import com.eactive.eai.transformer.message.MessageFactory;
import com.eactive.eai.transformer.util.Constants;
import com.eactive.eai.transformer.util.IConv;
import com.eactive.eai.transformer.util.XmlUtil;

public abstract class Composite extends Item {
	private static final long serialVersionUID = -4966715149524025425L;
	protected List<Item> childList;
    protected Map<String, Item> childMap;
	
	protected Composite() {
		super();
        childList = new ArrayList<>();
        childMap = new HashMap<>();	
	}	
	
	@Override
	public int getLength() {
		if (!isTop() && getIndex() == -1)
			return 0;
	
		int sum = 0;
        for (int i = 0; i < childList.size(); i++) { 
            Item item = (Item) childList.get(i);
            if (item.getIndex() > -1) {
            	sum += item.getLength();
            } 
        }
        return sum;
	}
	
	
	@Override
	public List<Item> getChildList() {
        return childList;		
	}
	
    @Override
    public void addChild(Item item) {
        Item lastItem = null;

        if (!childList.isEmpty()) {
            lastItem = childList.get(childList.size() - 1);
        }

        childList.add(item);
        childMap.put(item.getName() + "[" + item.getIndex() + "]", item);
        item.setParent(this);

        // Double Linked-List
        if (lastItem != null) {
            lastItem.setNext(item);
            item.setPrev(lastItem);
        }
    }	

    @Override
    public void addChild(Item prev, Item item) {
        int siblingIndex = childList.indexOf(prev);
        Item next = prev.getNext();

        childList.add(siblingIndex + 1, item);
        childMap.put(item.getName() + "[" + item.getIndex() + "]", item);
        item.setParent(this);

        prev.setNext(item);
        item.setPrev(prev);

        if (next != null) {
            item.setNext(next);
            next.setPrev(item);
        }
    }
    
    @Override
    public void removeChild(Item item) {
    	Item prev = item.getPrev();
    	Item next = item.getNext();

        childList.remove(item);
        childMap.remove(item.getName() + "[" + item.getIndex() + "]");

        if (prev != null) {
            prev.setNext(next);
        }
        
        if (next != null) {
        	next.setPrev(prev);
        }

        item.setParent(null);
        item.setPrev(null);
        item.setNext(null);
    }    
    
    @Override
    public void replaceChild(Item oldItem, Item newItem) {
        int pos = childList.indexOf(oldItem);
        Item prev = oldItem.getPrev();
        Item next = oldItem.getNext();

        childList.remove(oldItem);
        childList.add(pos, newItem);
        childMap.put(oldItem.getName() + "[" + oldItem.getIndex() + "]", newItem);
        newItem.setParent(this);
        
        if (prev != null) {
        	newItem.setPrev(newItem);
        	prev.setNext(newItem);
        }

        if (next != null) {
            newItem.setNext(next);
            next.setPrev(newItem);
        }
        
        /**
         * properties
         */
        newItem.setSeqNo(oldItem.getSeqNo());
        newItem.setDepth(oldItem.getDepth());
        newItem.setDescription(oldItem.getDescription());
        newItem.setDataTypeId(oldItem.getDataTypeId());
        newItem.setPath(oldItem.getPath());
        newItem.setLength(oldItem.getLength());		
        newItem.setRefInfo(oldItem.getRefInfo());
        newItem.setMask(oldItem.getMask());
        newItem.setMaskLength(oldItem.getMaskLength());
        newItem.setMaskOffset(oldItem.getMaskOffset());
        newItem.setOccType(oldItem.getOccType());
        newItem.setOccMax(oldItem.getOccMax());
        newItem.setOccMin(oldItem.getOccMin());
        newItem.setOccRef(oldItem.getOccRef());
    }   
    
    public Item getChild(int index) {
        return childList.get(index);
    }    
    
    @Override
    public int getChildCount() {
        return childList.size();
    }    

    @Override
    public Item getChildByName(String name) {
        return childMap.get(name);
    }
    
    public void clearValue() {
        for (int i = 0; i < childList.size(); i++) {
            Item child = (Item) childList.get(i);
            child.clearValue();
        }
    }
    
    public Item findItemByPath(String path) {
        for (int i = 0; i < childList.size(); i++) {
            Item child = childList.get(i);
            
            if (child.getIndex() == -1)
            	continue;
            
            Item result = child.findItemByPath(path);

            if (result != null) {
                return result;
            }
        }
        return null;
    }
    
    public void setDefaultValue(String defaultValue) {
    	// do nothing
    }
    
    public void initDefaultValue() {
        for (int i = 0; i < childList.size(); i++) {
            Item child = childList.get(i);
            child.initDefaultValue();
        }
    }
    
    public void initDepth(int depth) {
    	setDepth(depth);
        for (int i = 0; i < childList.size(); i++) {
            Item child = childList.get(i);
            child.initDepth(depth+1);
        }
    }
    
    public void initInclude() {
    	if (!isTop() && isMessage()) {
        	Message m = MessageFactory.getFactory().getMessage(name);
            getParent().replaceChild(this, m);
    		LayoutManager.getManager().addDependency(getParent().getRoot().getName(), name);
            
            for (int i = 0; i < m.getChildList().size(); i++) {
                (m.getChildList().get(i)).initInclude();
            }   
    	} else {
	        for (int i = 0; i < childList.size(); i++) {
	            Item child = childList.get(i);
	            child.initInclude();
	        }
    	}
    }
    
    /**
     * OccMin 설정
     * - min 갯수 만큼 sibling을 만든다.
     * - group 또는 message일 경우 child 에도 recursive 하게 작업해 준다.
     */
    public void initOccMin() {		
    	/**
    	 * [1] Top 일 경우
		 * [2] Top 이 아닐 경우
		 *     2.1. Group 이거나
		 *     2.2. Include 된 Message 이거나
    	 */
    	if (!isTop() && getIndex() == -1) {
    		int min = getOccMin();
            String itemPath = null;
            Item clone = null;

            /**
             * min 갯수 만큼 sibling(clone)을 만든다.
             */
            for (int j = 0; j < min; j++) {
                itemPath = normalizeFullName(getItemPath().substring(0, getItemPath().lastIndexOf('[')) + "[" + j + "]");
                clone = getTop().getOrCreateItem(itemPath);

                /**
                 * clone의 childList 에 recursive하게 작업해준다.
                 */
                List<Item> cList = clone.getChildList();

                for (int i = 0; i < cList.size(); i++) {
                    Item child = cList.get(i);
                    child.initOccMin();
                }
            }
        }
    	
        /**
         * 모든 composite에 대해 - childList 에 recursive하게 작업해준다.
         */
        for (int i = 0; i < childList.size(); i++) {
        	Item child = childList.get(i);
            child.initOccMin();
        }
    }
    
    public int initItemParentId(int seq, int parentId) {
        int id = 1000 + (seq++);
    	setItemId(id);
    	setParentId(parentId);

        for (int i = 0; i < childList.size(); i++) {
            Item child = childList.get(i);
            seq = child.initItemParentId(seq, itemId);
        }
        
        return seq;
    }
 
    public boolean existMask() {
		for (int i = 0; i < childList.size(); i++) {
			Item child = childList.get(i);
			if (child.existMask()) {
				return true;
			}
		}
		
		return false;
	}
    
    
    protected Item getItem(String itemPath) {
    	/**
   	 * leaf (FIELD | ATTRIBUTE) 일 경우, 
   	 * 또는 Group/Included Message를 Locating하는 경우
   	 */
       if (itemPath.indexOf(Types.FIELD_SEPARATOR) == -1) {
           //normalize 이후는 이런 일은 없겠지?
           if (itemPath.equals(name)) {
               return this;
           } else if (itemPath.endsWith("]") && itemPath.equals(name + "[" + index + "]")) { 
               return this;
           } else {
               return null;
           }

       /**
        * composite (GROUP | MESSAGE) 일 경우
        */
       } else {
	    	String prefix = null;
	        itemPath = itemPath.substring(itemPath.indexOf(Types.FIELD_SEPARATOR) + 1);
	        
	        if (itemPath.indexOf(Types.FIELD_SEPARATOR) != -1) {
	            prefix = itemPath.substring(0, itemPath.indexOf(Types.FIELD_SEPARATOR));
	
	            if (childMap.get(prefix) == null) {
	                return null;
	            }
	                
	            return (childMap.get(prefix)).getItem(itemPath);           	
	        } else {
	        	return childMap.get(itemPath);
	        }
       }
    }      
    
    protected Item getOrCreateItem(String itemPath) {
        /**
         * 1. leaf node 일 경우
         *    1.1 item 이름과 같을 경우 -> return this
         *    1.2 item 이름에 index를 붙인 이름과 같을 경우 (item[index]) -> return this
         *    1.3 위의 경우가 아닐 경우 -> return null
         */
    	int separatorPos = itemPath.indexOf(Types.FIELD_SEPARATOR);
        if (separatorPos == -1) {
            if (itemPath.equals(name)) {
                return this;
            } else if (itemPath.equals((new StringBuffer(32)).append(name).append("[").append(Integer.toString(index)).append("]").toString())) {
                return this;
            } else {
                return null;
            }

        /**
         * 2. non-leaf node 일 경우
         *    예) non-leaf node (A[0].B[9].C[1])
         */
        } else {
            String prefix = null;
            itemPath = itemPath.substring(separatorPos + 1);               	

        	/**
        	 * 2.1 이름에서 첫번째 item을 떼어낸 후에도 separator(.) 가 남아 있을 경우
        	 *     예) A[0].B[9].C[1] -> B[9].C[1]
        	 */
            int sepIndex = itemPath.indexOf(Types.FIELD_SEPARATOR);
            if (sepIndex != -1) {
                prefix = itemPath.substring(0, sepIndex);
                Item child = childMap.get(prefix);
                
                if (child == null) {
                	String firstItemPath = getName() + "[" + getIndex() + "]" + Types.FIELD_SEPARATOR 
                			+ prefix.substring(0, prefix.lastIndexOf("[")) + "[-1]";
                    Item item = getItem(firstItemPath);
                    
                    if (item == null) {
                    	throw new InvalidNodeException("레이아웃("+ getTop().getName() + ")에 " + firstItemPath + " 항목이 없음.", getTop(), this, null);
                    }
                    
                    Item sibling = item.getSibling(prefix);

                    if (sibling != null) {
                        createItem(sibling, prefix, true);
                    } else {
                    	throw new MessageException("레이아웃항목 생성 불가(" + prefix + ")");
                    }
                    
                    child = childMap.get(prefix);
                }
                if(child != null) {
                	return child.getOrCreateItem(itemPath);
                }
                else {
                	return null;
                }
        	} 
            /**
        	 * 2.1 이름에서 첫번째 item을 떼어낸 후 separator(.) 가 남아 있지 않을 경우
        	 *     예) B[9].C[1] -> C[1]
        	 */
            else {
                Item child = childMap.get(itemPath);
                if (child == null) {              	
                	String firstItemPath = getName() + "[" + getIndex() + "]" + Types.FIELD_SEPARATOR 
                			+ itemPath.substring(0, itemPath.lastIndexOf("[")) + "[-1]";
                    Item item = getItem(firstItemPath);
                    if (item == null) {
                    	throw new InvalidNodeException("레이아웃("+ getTop().getName() + ")에 " + firstItemPath + " 항목이 없음.", getTop(), this, null);
                    }
                    
                	Item sibling = item.getSibling(itemPath);

                    if (sibling != null) {                  	
                        return createItem(sibling, itemPath, true);
                    }
				}
                return child;
            }
        }
    }
    
    @Override
    public Object clone() {
    	Composite clone = (Composite) super.clone();
    	
        clone.childList = new ArrayList<>();
        clone.childMap = new HashMap<>();
		
        String messageType = this.getTop().getType();
        
        boolean isVariableType = false;
        if(LayoutTypeKey.JSON.equals(messageType) 
        	|| LayoutTypeKey.XML.equals(messageType)) {
        	isVariableType = true;
        }
        
        for (int i = 0; i < childList.size(); i++) {			
			Item child = getChild(i);
			if(isVariableType) {
				// FIX-ME : 2021.10.08
				// JSON 이중 가변 그리드일 경우 빈값이 생기는 문제
	        	if(child.getNodeTypeId() == NODE_GROUP 
						&& !(child.getOccMin() ==child.getOccMax() && child.getOccMin() > 1 )) {							
					if(child.index > 0) {
						continue;
					}
				}
				// link를 끊는다.
				clone.setNext(null);
			}
			clone.addChild((Item) child.clone());			 
		}
		
		return clone;
    }
    
    public String toString(String tab, boolean bShowAllFlag) {	
    	/**
    	 * index==-1 인 경우 index>=0 인 sibling 이 있으면 표시하지 않는다. 
    	 */
    	if (index == -1 && next != null && getName().equals(next.getName())) {
    		return "";
    	}
        
        StringBuilder sb = new StringBuilder();

        /**
         * name
         */
        sb.append("+ " + getClass().getSimpleName() + " - ").append(name).append("[");
        sb.append(index + "").append("] ");

        /**
         * length
         */
        sb.append("{" + (getRoot() instanceof BytesMessage ? getLength()+"" : "") + "} ");
    	
        /**
         * occurrence
         */
        sb.append("(occ=").append(occType);
        sb.append(",").append(occMin);
        sb.append(",").append(occMax);
        sb.append(",").append(occRef);
        sb.append(") ");
        
        
        /**
         * value
         */
        Object newValue = null;
        if (value instanceof byte[]) {
        	if (getRoot() instanceof EbcdicMessage) {
        		newValue = IConv.ebcdicToASCIISOSISpaceEX((byte[]) value);
        	} else {
        		newValue = new String((byte[]) value);
        	}
        } else if (value instanceof String) {
        	if (getRoot() instanceof EbcdicMessage) {
        		newValue =  IConv.ebcdicToASCIISOSISpaceEX(((String) value).getBytes());
        	} else {
        		newValue = (String)value;
        	}
        }        
        else {
        	newValue = value;
        }
        
        sb.append("(");
        if (newValue != null) {
            sb.append(newValue.getClass().getName().substring(newValue.getClass().getName()
                                                          .lastIndexOf(Types.FIELD_SEPARATOR) +
                                                          1));
        }
        sb.append(":");
        sb.append(newValue);
        sb.append(") ");
        
        /* 
         * PATH - XML namespace  xmlns:prefix="URI"
         * xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.05"
         * xmlns:a="https://www.w3.org/TR/html5/"
         */
        if( (path != null) && (path.length() > 0) ) {
	        sb.append(" path(");
	        sb.append(path);
	        sb.append(") ");
        }
        
        /**
         * FMLMessage only -> class name
         */
//        if (this instanceof FMLMessage || this instanceof ObjectMessage) {
//        	sb.append(" (class=" + ((FMLMessage) this).getRefInfo() + ")");
//        }
//        
        /**
         * composite only
         */
        for (int i = 0; i < childList.size(); i++) {
        	if (!((Item) childList.get(i)).toString("", false).equals("")) {
                sb.append("\n" + Constants.TAB + " | " + tab + ((Item) childList.get(i)).toString(tab + Constants.TAB, false));
            }
        }
        
        return sb.toString();
    }
    
    public String toXMLString(String tab, boolean bShowAllFlag) {	
    	/**
    	 * index==-1 인 경우 index>=0 인 sibling 이 있으면 표시하지 않는다. 
    	 */
    	if (index == -1 && next != null && getName().equals(next.getName())) {
    		return "";
    	}
        
        StringBuilder sb = new StringBuilder();
        /**
         * name
         */
        sb.append("<"+name +" desc=\""+description+"\">");
        
        /**
         * value
         */
        Object newValue = null;
        if (nodeTypeId != NODE_MESSAGE){
	        if (value instanceof byte[]) {
	        	if (getRoot() instanceof EbcdicMessage) {
	        		newValue =  IConv.ebcdicToASCIISOSISpaceEX((byte[]) value);
	        	} else {
	        		newValue = new String((byte[]) value);
	        	}
	        } else if (value instanceof String) {
	        	if (getRoot() instanceof EbcdicMessage) {
	        		newValue =  IConv.ebcdicToASCIISOSISpaceEX(((String) value).getBytes());
	        	} else {
	        		newValue = value;
	        	}        
	        }  else if (value instanceof BigDecimal) {
	        	value = ((BigDecimal) value).toPlainString();
	        	if (getRoot() instanceof EbcdicMessage) {
	        		newValue =  IConv.ebcdicToASCIISOSISpaceEX(((String) value).getBytes());
	        	} else {
	        		newValue = value;
	        	}        
	        }
	        else {
	        	newValue = value;
	        }
	        // Group 의 value가 null일 경우에는 표시하지 않는다.
	        if (!(nodeTypeId == NODE_GROUP && newValue == null)) {
	        	sb.append(XmlUtil.escapeTags((String)newValue));
	        }
        }
        
        /**
         * composite only
         */
        for (int i = 0; i < childList.size(); i++) {
        	if (!(childList.get(i)).toString("", false).equals("")) {
                sb.append("\n" + Constants.TAB + "   " + tab + (childList.get(i)).toXMLString(tab + Constants.TAB, false));
            }
        }
        sb.append("\n" + "   " + tab  );
        sb.append("</"+name+">");
        
        return sb.toString();
    }     
}
