package com.eactive.eai.transformer.message;

import java.util.Iterator;

import com.eactive.eai.transformer.layout.Item;
import com.eactive.eai.transformer.layout.Types;
import com.eactive.eai.transformer.util.ByteUtil;
import com.eactive.eai.transformer.util.CommonLib;
import com.eactive.eai.transformer.util.Constants;
import com.eactive.eai.transformer.util.Logger;


/**
 * <pre>
 * DELIMETER (전문) 형식의 데이터를 변환하기 위한 메시지.
 *
 * [제약 사항]
 * 현재는 0x09로 만 파싱. row 마지막에 0x0D확인필요
 *
 * </pre>
 */
public class DelimiterMessage extends Message {
	private static final long serialVersionUID = -2256252788382494416L;

	private static Logger logger = Logger.getLogger("transformer");

	// ┌
	public static final byte DEL1 = (byte)0xA6;
	public static final byte DEL2 = (byte)0xA3;
	public static final byte NEWLINE = 0x0D;
    public static final byte[] DELIMITER =  {DEL1, DEL2};
    public static final byte[] ROW_DELIMITER =  {NEWLINE};
    public static final String ENCODING = "euc-kr";
    protected boolean bStaled = false;

    private byte[] data;

    public DelimiterMessage() {
        super();
    }

    public void createObject() {
        data = new byte[0];
    }
    
    protected void composeData(Item item, Object value) {
        if (item.getIndex() < 0)
            return;
        
        if (!bStaled) {
            bStaled = true;
        }
    }

    //--------------------------------------------------------------------------
    public Object getData() throws InvalidDataTypeException {
        if (bStaled) {
            try {
                data = toByteArray();
            } catch(Exception e) {
                throw new InvalidDataTypeException(e.getMessage(),"DelimiterMessage.getData() method", e);   
            }
            bStaled = false;
        }
        
        return getDataBytes();
    }
    
    protected byte[] toByteArray() {
    	byte[] bytes = new byte[0];
		for (Iterator iter = iterator(); iter.hasNext();) {
			Item item = (Item) iter.next();

			/**
			 * index == -1 인 경우는 무시한다
			 */
			if (item == null || item.getIndex() == -1) {
				continue;
			}

	        switch (item.getNodeTypeId()) {
			case NODE_FIELD:
	            if (item.getValue() == null) {
	            	bytes = merge(bytes, new byte[0]);
	            } else if (item.getValue() instanceof String) {
	            	bytes = merge(bytes, ((String)item.getValue()).trim().getBytes());
	            } else if (item.getValue() instanceof Number) {
	            	bytes = merge(bytes, (((Number) item.getValue()).toString().getBytes()));
	            } else {
	            	bytes = merge(bytes, (byte[]) item.getValue());
	            }	            
				break;
	        }
		}
		
		return bytes;
    }    

    protected void doSetData(Object data) {
    	doSetData(data ,false);
    }
    protected void doSetData(Object data ,boolean refCountLimit) {
    	if (data instanceof String) {
    		this.data = ((String)data).getBytes();
    	}else if (data instanceof byte[]) {
    		this.data = (byte[])data;
        } else {
            if (data == null) {
                throw new InvalidDataTypeException("Not able to generate Delimiter messages with data. Input value is null.", data);
            } else {
                throw new InvalidDataTypeException("Not able to generate Delimiter messages with data. data type = " + data.getClass().getName(), data);
            }
        }
        
        byte[] _data = ByteUtil.copy(this.data);
        byte[] result = setDataString(_data , refCountLimit);
    }
    
    // only for BytesMessage & CommonBytesMessage
    protected byte[] setDataString(byte[] data , boolean refCountLimit) {
    	Item item = null;

    	Iterator iter = iterator();
		while(iter.hasNext()) {	
			item = (Item) iter.next();
			/**
			 * occType = * | + | (occMax>occMin) 일 경우
			 * occRef를 참조하여 그 갯수만큼 item을 생성한다.
			 */
	        if (item.getIndex()==-1 && ("*".equals(item.getOccType()) || "+".equals(item.getOccType()) || item.getOccMax()>item.getOccMin())) {
	            int occCount = 0;
	            try {
	            	occCount = item.getRoot().getInt(item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getOccRef());
	            }
	            catch(Exception oe) {
	            	throw new InvalidAccessException(item.getName() +" - Repeat reference information value is not a number - " + " : " + oe.getMessage() , item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getOccRef());
	            }

	            if (occCount < item.getOccMin() 
	            	|| (!"*".equals(item.getOccType()) && !"+".equals(item.getOccType()) && occCount > item.getOccMax())) { 
	                throw new InvalidAccessException("Number of iterations out of [OccMin ~ OccMax] , " + item.getName(), item.getItemPath());
	            }

	            item.createItem(item, 
	            		item.getRoot().normalizeFullName(item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getName() + "[" + (occCount-1) + "]"), 
	            		false);

	            /**
	             * item 이 속해있는 childList 크기가 증가로 인해, iterator를 다시 생성한다.
	             */
	            iter = item.getRoot().iterator();
	            while(iter.hasNext()) {	
	            	if (item == iter.next()) {
	            		break;
	            	}
	            }
	        }
	        
			/**
			 * index == -1 인 경우는 무시한다
			 */
			if (item.getIndex() == -1) {		
				continue;
			}

	        switch (item.getNodeTypeId()) {
			case NODE_FIELD:
				try {
					byte[] c = cut(data);
	            	item.setValue(c);	            	
				} catch(Exception e) {
					StringBuilder sb = new StringBuilder();
		        	sb.append("MessageLayout mapping - Data size error ("+e.getMessage()+")");
		        	sb.append("\n");
		        	sb.append("MessageLayout="+item.getItemPath());
		           throw new MessageException(sb.toString());
				}
				
				try {
	            	data = sub(data);
				} catch(Exception e) {
					StringBuilder sb = new StringBuilder();
					sb.append("MessageLayout - Data setting error ("+ e.getMessage() + ")");
		        	sb.append("\n");
		        	sb.append("MessageLayout="+item.getItemPath());
		            throw new MessageException(sb.toString());
				}
	            break;
			}
	        //grid의 마지막 컬럼
//			if (item.getParent().isGroup() && item.getNext() == null) {
//				//참조컬림이 있는경우 or max count가 있는경우
//				if (!"".equals(item.getParent().getOccRef()) || item.getParent().getOccMax() > 1 ) {
//					data = ByteUtil.sub(data,1);
//				}
//			}
		}		

        if (data.length > 0) {
        	StringBuilder sb = new StringBuilder();
        	sb.append("MessageLayout mapping - Data delimiter error ");
        	sb.append("\n");
        	sb.append("MessageLayout="+item.getItemPath());
        	sb.append(", remain data ="+new String(data));
            throw new MessageSetDataOverflowException(sb.toString());
        }
        
        return data;
    }        
    

    //--------------------------------------------------------------------------
    // Additional Getter/Setter 

    public byte[] getDataBytes() throws InvalidDataTypeException {
        return data;
    }

    public int getDataLength() {
        if (data == null) {
            return -1;
        } else {
            return data.length;
        }
    }
    
    public String toLogString() {
    	byte[] buff = makeLogString();
        return new String(buff);
    }

    protected byte[] makeLogString() {
	byte[] bytes = new byte[0];
	
	for (Iterator iter = iterator(); iter.hasNext();) {
		Item item = (Item) iter.next();

		/**
		 * index == -1 인 경우는 무시한다
		 */
		if (item.getIndex() < 0) {
			continue;
		}

        switch (item.getNodeTypeId()) {
			case NODE_FIELD:
				if ("Y".equals(item.getMask())) {
	            	bytes = merge(bytes, maskString2(item.getValue(), item.getMaskLength()).getBytes());	   
	            } else {
		            if (item.getValue() == null) {
		            	bytes = merge(bytes, new byte[0]);
		            } else if (item.getValue() instanceof String) {
		            	bytes = merge(bytes, ((String)item.getValue()).trim().getBytes());
		            } else if (item.getValue() instanceof Number) {
		            	bytes = merge(bytes, (((Number) item.getValue()).toString().getBytes()));
		            } else {
		            	bytes = merge(bytes, (byte[]) item.getValue());
		            }	
	            }
	            break;
			case NODE_GROUP:
			case NODE_MESSAGE:				
				break;
			default :
				break;
        }
        //grid의 마지막 컬럼
//		if (item.getParent().isGroup() && item.getNext() == null) {
//			//참조컬림이 있는경우 or max count가 있는경우
//			if (!"".equals(item.getParent().getOccRef()) || item.getParent().getOccMax() > 1 ) {
//				bytes = ByteUtil.merge(bytes, ROW_DELIMITER);
//			}
//		}
        
	}
	
	return bytes;
    }    

    @Override
    public String toString() {
        // if staled -> reorganize data
        try {
            getData();
        } catch (InvalidDataTypeException e) {
            logger.error("[DelimiterMessage] " + e.getMessage(), e);
        }

        StringBuilder sb = new StringBuilder();
        if ("DelimiterMessage".equals(getClass().getSimpleName())) {
        	sb.append(getName() + " (DelimiterMessage)");
        }
        sb.append(super.toString());
        if (data != null) {
            sb.append("\n" + Constants.TAB + " | data = " + CommonLib.hexDumpTab(data));
        }
        sb.append("\n" + Constants.TAB + " +--------------------------------------------------------------------------------------------------+");

        return sb.toString();
    }
    
    private byte[] cut(byte[] str) {
    	 int idx0 = indexOf(str, DELIMITER);  
    	 return ByteUtil.cut(str, idx0);
    }
    private byte[] sub(byte[] str) {
    	int idx0 = indexOf(str, DELIMITER); 
    	return ByteUtil.sub(str,idx0+DELIMITER.length);
    }
    private byte[] merge(byte[] data,byte[] str) {
    	byte[] data2 = ByteUtil.merge(data, str);
    	return ByteUtil.merge(data2, DELIMITER);
    }
    private int indexOf(byte[] outerArray, byte[] smallerArray) {
	    for(int i = 0; i < outerArray.length - smallerArray.length+1; ++i) {
	        boolean found = true;
	        for(int j = 0; j < smallerArray.length; ++j) {
	           if (outerArray[i+j] != smallerArray[j]) {
	               found = false;
	               break;
	           }
	        }
	        if (found) return i;
	     }
	   return -1;  
	}
}
