package com.eactive.eai.transformer.message;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
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.IConv;


/**
 * <pre>
 * BYTE ARRAY(전문) 형식의 데이터를 변환하기 위한 메시지.
 * 전문의 길이는 고정 및 가변을 처리할 수 있게 되어 있음.
 *
 * [제약 사항]
 * 현재는 고정 필드에 대해서만 처리 가능.
 *
 * [PATH 정의]
 * PATH           = <ITEM_TYPE> + <STATIC_PATH> + <DYNAMIC_PATH>
 * ITEM_TYPE      = 'F' | 'G' | 'M'
 * STATIC_PATH    = OFFSET
 * </pre>
 */
public class DynamicFieldBytesMessage extends BytesMessage {
    public static final String NAME = "DFBYTES";
    protected byte[] data;
    protected boolean bStaled = false;

    public DynamicFieldBytesMessage() {
        super();
    }

    @Override
    public void createObject() {
        data = new byte[0];
    }
    
    @Override
    public Object getData() throws InvalidDataTypeException {
        if (bStaled) {
            try {
                data = toByteArray();
            } catch(Exception e) {
                throw new InvalidDataTypeException(e.getMessage(),"BytesMessage.getData() method", e);   
            }
            bStaled = false;
        }

        return getDataBytes();
    }
    
    @Override
    protected byte[] toByteArray() {
    	try (
    		ByteArrayOutputStream bos = new ByteArrayOutputStream();
    		){
			for (Iterator iter = iterator(); iter.hasNext();) {
				Item item = (Item) iter.next();
	
				/**
				 * index == -1 인 경우는 무시한다
				 */
				if (item == null || item.getIndex() == -1) {
					continue;
				}
	
		        if(item.getNodeTypeId() == NODE_FIELD) {
				    if (item.getValue() == null) {
		            	bos.write( new byte[item.getLength()] );
		            } else if (item.getValue() instanceof String) {
		            	bos.write( ByteUtil.padding((String) item.getValue(), item.getLength()) );
		            } else if (item.getValue() instanceof Number) {
		            	bos.write( ByteUtil.padding(((Number) item.getValue()).toString(), item.getLength()) );
		            } else {
		            	bos.write( ByteUtil.padding((byte[]) item.getValue(), item.getLength()) );
		            }	            
				}
			}			
			return  bos.toByteArray();			 
    	} catch(Exception e) {
    		logger.error("retrun zero-bytes.", e);
    		return new byte[0];
    	}
    }    

    @Override
    protected void doSetData(Object data) {
        this.data = (byte[]) data;
        byte[] _data = ByteUtil.copy(this.data);
        
        if(_data != null) {
        	setDataBytes((byte[]) _data );
        }
    }
    
    @Override
    protected byte[] setDataBytes(byte[] data ) {
    	int originalLength = data.length;
    	Item item = null;
    	Iterator iter = iterator();
		while(iter.hasNext()) {
			item = (Item) iter.next();
			
			if(item == null) {
				continue;
			}
			/**
			 * occType = * | + | (occMax>occMin) 일 경우
			 * occRef를 참조하여 그 갯수만큼 item을 생성한다.
			 */
	        if (item.getIndex()==-1 && ("*".equals(item.getOccType()) || "+".equals(item.getOccType()) || item.getOccMax()>item.getOccMin())) {
	            int occCount = 0;
	            String path ="";
	            try {
	            	path = getOccRefItem(item);
	            	occCount = item.getRoot().getInt(path);
	            }
	            catch(Exception oe) {
	            	throw new InvalidAccessException(item.getName() +"의 반복참조정보 값이 숫자가 아닙니다 - " + " : " + oe.getMessage() , path);
	            }
	            
	            if (occCount < item.getOccMin() 
	            	|| (!"*".equals(item.getOccType()) && !"+".equals(item.getOccType()) && occCount > item.getOccMax())) { 
	                throw new InvalidAccessException("반복횟수가 OccMin~OccMax 의 범위를 벗어납니다. , " + item.getName(), item.getItemPath());
	            }
	            
	            
	            /**
	             * 반복횟수 참조정보가 비정상적으로 클때 방어로직
	             * 그리드 오류처리로직) 자식배열 길이합계 * 반복횟수 참조정보 > 셋팅하는 실제 데이터 
	             * 필드   오류처리조직) 필드 길이            * 반복횟수 참조정보 > 셋팅하는 실제 데이터
	             */
	            int sumChildLength = 0;
	            Item childItem= null;
	            if (item.isGroup()){
		            for(int i=0;i<item.getChildList().size();i++){
		            	childItem = item.getChild(i);
		            	if (childItem.getIndex() == -1){
		            		continue;
		            	}
		            	sumChildLength += item.getChild(i).getLength();
		            }
		            if (sumChildLength * occCount > data.length){
		            	throw new InvalidAccessException(item.getName() +"의 반복참조정보 값("+occCount+") * 자식배열 길이합계("+sumChildLength+") = 계산된길이("+(sumChildLength * occCount)+") 보다 실제데이터 길이("+data.length+")가 작습니다. "  , path);
		            }
	            }else{
	            	if (item.getLength() * occCount > data.length){
		            	throw new InvalidAccessException(item.getName() +"의 반복참조정보 값("+occCount+") * 길이("+item.getLength()+") = 계산된길이("+(item.getLength() * occCount)+") 보다 실제데이터 길이("+data.length+")가 작습니다. "  , path);
		            }
	            }	            
	            item.createItem(item, 
	            		item.getRoot().normalizeFullName(item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getName() + "[" + (occCount-1) + "]"), 
	            		false);

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

	        if (item.getNodeTypeId() == NODE_FIELD) {
				try {
	            	item.setValue(ByteUtil.cut(data, item.getLength()));
				} catch(Exception e) {
					StringBuilder sb = new StringBuilder();
		        	sb.append("전문레이아웃 매핑 중 데이터 사이즈 오류 ("+e.getMessage()+")");
		        	sb.append("\n");
		        	sb.append("전문레이아웃="+item.getItemPath());
		           throw new MessageException(sb.toString());
				}
				
				try {
	            	data = ByteUtil.sub(data, item.getLength());
				} catch(Exception e) {
					StringBuilder sb = new StringBuilder();
					sb.append("전문레이아웃 매핑 중 데이터 셋팅 오류 ("+ e.getMessage() + ")");
		        	sb.append("\n");
		        	sb.append("전문레이아웃="+item.getItemPath());
		            throw new MessageException(sb.toString());
				}
	        }
		}		

        if (data.length > 0) {
        	StringBuilder sb = new StringBuilder();
        	sb.append("전문레이아웃 매핑 중 데이터 사이즈 오류 (정의한 길이=" + (originalLength-data.length) +", 받은 길이="+ originalLength + ")");
        	sb.append("\n");
        	sb.append("전문레이아웃="+(item==null?"":item.getItemPath()));
            throw new MessageSetDataOverflowException(sb.toString());
        }
        
        return data;
    }        
    

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

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

    @Override
    public int getDataLength() {
        if (data == null) {
            return -1;
        } else {
            return data.length;
        }
    }

    @Override
    public void clearData() {
        clearData((byte) 0x00);
        clearValue();
    }

    @Override
    public void clearData(byte fillByte) {
        if (data != null) {
            for (int i = 0; i < data.length; i++) {
                data[i] = fillByte;
            }
        }
    }
    
    @Override
    public String toLogString() {
        return toLogString(false);
    }

    @Override
    public String toLogString(boolean ebcdic) {
        if (ebcdic) {
            byte[] buff = makeBytesLogString();
            return IConv.ebcdicToASCII(buff);
        } else {
            byte[] buff = makeBytesLogString();
            return new String(buff);
        }
    }    
    
    @Override
    protected byte[] makeBytesLogString() {
    	try(
    		ByteArrayOutputStream bos = new ByteArrayOutputStream();
    	) {
			for (Iterator iter = iterator(); iter.hasNext();) {
				Item item = (Item) iter.next();
	
				/**
				 * index == -1 인 경우는 무시한다
				 */
				if (item == null || item.getIndex() < 0) {
					continue;
				}
	
		        if (item.getNodeTypeId() == NODE_FIELD) {
					if ("Y".equals(item.getMask())) {
						String ms = maskString(item.getLength());
						if(ms == null) ms = "";
						bos.write( ms.getBytes() );	            	
		            } else {
			            if (item.getValue() == null) {
			            	bos.write( new byte[item.getLength()] );
			            } else if (item.getValue() instanceof String) {
			            	bos.write( ByteUtil.padding((String) item.getValue(), item.getLength()) );
			            } else if (item.getValue() instanceof Number) {
			            	bos.write( ByteUtil.padding(((Number) item.getValue()).toString(), item.getLength()) );
			            } else {
			            	bos.write( ByteUtil.padding((byte[]) item.getValue(), item.getLength()) );
			            }
		            }
		        }
			}
			return  bos.toByteArray();			 
    	} catch(Exception e) {
    		logger.error("retrun zero-bytes.", e);
    		return new byte[0];
    	}    	
    }    

    @Override
    public String toString() {
        try {
            getData();
        } catch (InvalidDataTypeException e) {
            logger.error("[DynamicFieldBytesMessage] " + e.getMessage(), e);
        }

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

        return sb.toString();
    }
}
