package com.eactive.eai.transformer.message;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Stack;

import org.apache.commons.lang3.StringUtils;

import com.eactive.eai.transformer.SystemKeys;
import com.eactive.eai.transformer.layout.Item;
import com.eactive.eai.transformer.layout.LayoutTypeKey;
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 BytesMessage extends Message {
	private static final long serialVersionUID = 6266627403215834972L;
	public static final String NAME = LayoutTypeKey.BYTES;
    protected byte[] data;
    protected boolean bStaled = false;
    
    // KBANK 표준전문의 경우 UTF-8을 사용
    //TODO: 설정으로 처리 가능해지는 경우 해당 로직 반영 필요
    private String defaultCharset = StringUtils.defaultString(SystemKeys.getBytesMessageCharset(), "utf-8");
    
    public BytesMessage() {
        super();
    }

    public void createObject() {
        data = new byte[0];
    }
    
    // 전문일 경우 숫자형문자로 정의
    @Override
    public int getInt(String itemPath) { 	
        try {
            Item item = getItem(itemPath, true);

            if (item == null) {
                throw new InvalidAccessException("레이아웃항목이 존재하지 않음", itemPath);
            }

            Object value = item.getValue();
            if (value == null) {
                return 0;
            } else if (value instanceof byte[]) {
            	return Integer.parseInt(new String(((byte[]) value)).trim());
            } else {
                throw new InvalidAccessException("레이아웃항목의 값이 숫자가 아님", itemPath);
            }
        } catch (Exception e) {
            throw new InvalidAccessException(e.getMessage(), itemPath, this, e);
        }
    }    
    
    @Override
    public void setInt(String itemPath, int value) {
    	int newValue =  value; 
        super.setInt(itemPath, newValue);
        
        Item item = getOrCreateItem(itemPath, true);
        try {
        	setBytes(itemPath, StringUtils.leftPad(String.valueOf(value), item.getLength(), "0").getBytes());
        } catch(Exception e) {
        	throw new MessageException(e.getMessage(), e);
        }
    }
    
    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(),"BytesMessage.getData() method", e);   
            }
            bStaled = false;
        }

        return getDataBytes();
    }
    
    protected byte[] toByteArray() {
    	ByteArrayOutputStream bos = new ByteArrayOutputStream();
    	try {
			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) {
		            	// NULL -> space padding
		            	bos.write( ByteUtil.padding(new byte[0], 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];
    	}
    	finally {
    		if(bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					if(logger.isWarnEnabled()) logger.warn("bos.close failed.", e);
				}
    		}
    	}
    	
    }    

    protected void doSetData(Object data) {
    	if(data instanceof byte[]) {
        	this.data = (byte[]) data;
        }
        if(data instanceof String) {
        	try {
				this.data = ((String)data).getBytes(defaultCharset);
			} catch (UnsupportedEncodingException e) {
				logger.warn("charset error, use default charset", e);
				this.data = ((String)data).getBytes();
			}
        }
        setDataBytes( this.data );
    }
    
    // only for BytesMessage & CommonBytesMessage
    protected byte[] setDataBytes(byte[] data ) {
    	int originalLength = data.length;
    	Item item = null;
    	Stack<Item> itemStack = new Stack<Item>();
    
    	int startPosition = 0;
        int  remainSize = originalLength;
        
        addItemToStack(itemStack, childList);
		while( ! itemStack.empty()) {
			item = itemStack.pop();
			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() +"의 반복참조정보 값이 숫자가 아닙니다 - " + path + " : " + 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+")가 작습니다. "  , item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getOccRef());
//		            	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+")가 작습니다. "  , item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getOccRef());
//	            		throw new InvalidAccessException(item.getName() +"의 반복참조정보 값("+occCount+") * 길이("+item.getLength()+") = 계산된길이("+(item.getLength() * occCount)+") 보다 실제데이터 길이("+data.length+")가 작습니다. "  , path);
//		            }
//	            }
	            
	            ArrayList<Item> itemList = new ArrayList<>();
	            item.createItem(item, 
	            		item.getRoot().normalizeFullName(item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getName() + "[" + (occCount-1) + "]"), 
	            		false, itemList);
	            
	            addItemToStack(itemStack, itemList);
	        }
	        
			/**
			 * index == -1 인 경우는 무시한다
			 */
			if (item.getIndex() == -1) {		
				continue;
			}

	        switch (item.getNodeTypeId()) {
			case NODE_FIELD:
				try {
	            	item.setValue(ByteUtil.cut(data, startPosition, item.getLength()));
	            	startPosition += item.getLength();
	            	remainSize -= item.getLength();
	            	if (SystemKeys.isValidationEnabled() && item.getDecimalPointLength() > 0) {
	            		String strVal = new String((byte[]) item.getValue(), defaultCharset).trim();
	            		try {
	            			new BigDecimal(strVal);
	            		} catch (NumberFormatException nfe) {
	            			throw new MessageException(
	            				String.format("decimal 필드 숫자형 오류: %s = [%s]",
	            					item.getItemPath(), strVal));
	            		}
	            	}
				} catch(MessageException me) {
					throw me;
				} catch(Exception e) {
					StringBuilder sb = new StringBuilder();
		        	sb.append("전문레이아웃 매핑 중 데이터 사이즈 오류 ("+e.getMessage()+")");
		        	sb.append("\n");
		        	sb.append("전문레이아웃="+item.getItemPath());
		           throw new MessageException(sb.toString());
				}
	            break;
			case NODE_GROUP:
	            addItemToStack(itemStack, item.getChildList());
	            break;
			}
		}		

        if (remainSize > 0) {
        	StringBuilder sb = new StringBuilder();
        	String itemPath = (item==null?"":item.getItemPath());
        	sb.append("setData() : " + itemPath + " : data size가 맞지 않습니다. (overflow) ");
        	sb.append("전문레이아웃 매핑 중 데이터 사이즈 오류 (정의한 길이=" + (originalLength-remainSize) +", 받은 길이="+ originalLength + ")");
        	sb.append("\n");
        	sb.append("전문레이아웃="+itemPath);
            throw new MessageSetDataOverflowException(sb.toString());
        }
        
        return data;
    }        

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

    public byte[] getDataBytes() throws InvalidDataTypeException {
        /*
        if (isNotVariableDataUpdated()) {
            validateVariable();
        }
        */
        return data;
    }

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

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

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

    public String toLogString(boolean ebcdic) {
        if (ebcdic) {
            byte[] buff = makeBytesLogString();
            return IConv.ebcdicToASCIIWithSOSIEX(buff);
        } else {
            byte[] buff = makeBytesLogString();
            try {
				return new String(buff, defaultCharset);
			} catch (UnsupportedEncodingException e) {
				logger.warn("unsuppotred charset : " + defaultCharset, e);
				return new String(buff);
			}
        }
    }    
    
    protected byte[] makeBytesLogString() {
    	ByteArrayOutputStream bos = new ByteArrayOutputStream();
    	try {
			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()) && item.getValue() != null) {
						String mValue = maskString2(item.getValue(), item.getMaskLength(), item.getMaskOffset());						
						bos.write( ByteUtil.padding(mValue, item.getLength()) );
		            } else {
			            if (item.getValue() == null) {			            	
			            	bos.write( ByteUtil.padding(new byte[0], 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];
		}
		finally {
			if(bos != null)
				try {
					bos.close();
				} catch (IOException e) {
					if(logger.isWarnEnabled()) logger.warn("bos.close failed.", e);
				}
		}
    }    

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

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

        return sb.toString();
    }
}
