package com.eactive.eai.transformer.message;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.map.HashedMap;

import com.eactive.eai.transformer.layout.Item;
import com.eactive.eai.transformer.layout.Layout;
import com.eactive.eai.transformer.util.ISO8583DumpUtil;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.codecs.CompositeField;
import com.solab.iso8583.parse.FieldParseInfo;

/**
 * <pre>
 * ISO 8583(전문) 형식의 데이터를 변환하기 위한 메시지.
 *
 * [참조]
 * https://en.wikipedia.org/wiki/ISO_8583
 * </pre>
 */
@SuppressWarnings("serial")
public class ISO8583Message extends BytesMessage {
	public static final String NAME = "ISO8583";
	public static final String FIELD_NAME_MESSAGE_TYPE = "messageType";
	public static final String FIELD_NAME_PRIMARY_BITMAP = "primaryBitMap";
	protected Map<String, Item> childMap_ = null;
	private static HashMap<Integer, FieldParseInfo> fullSpecMap = null;
	private static HashMap<Integer, String> fullFieldNameMap = null;
	private int isoHeaderLength = 0;

	public ISO8583Message() {
		super();
	}

	@SuppressWarnings("unchecked")
	public Map<String, Item> getChildMap() {
		if (childMap_ == null) {
			childMap_ = new HashedMap();
			for (Object object : childList) {
				Item item = (Item) object;
				if (item.getIndex() > -1) {
					childMap_.put(item.getName(), item);
				}
			}
		}

		return childMap_;
	}

	@Override
	public Item getChildByName(String name) {
		return getChildMap().get(name);
	}

	@Override
	public Object getData() throws InvalidDataTypeException {
		return super.getDataBytes();
	}

	protected byte[] setDataBytes(byte[] data) {
		try {
			MessageFactory<IsoMessage> mf = createMessageFactory();
			setParseMapFromLayout(data, mf, layout, getChildMap(), getIsoHeaderLength());
			IsoMessage m = mf.parseMessage(data, getIsoHeaderLength(), mf.isBinaryHeader());
			assignRepresentativeItemValue(data, m, getIsoHeaderLength());

			if (logger.isDebugEnabled())
				logger.debug(
						"Parsed [" + getLayoutName() + "] ISO Message: \n" + ISO8583DumpUtil.dumpIsoMessage(m, this));

			for (int i = 2; i <= 128; i++) {
				assignItemValue(i, m);
			}
		} catch (Exception e) {
			throw new MessageException(e.getMessage(), e);
		}

		return data;
	}

	public MessageFactory<IsoMessage> createMessageFactory() {
		MessageFactory<IsoMessage> mf = new MessageFactory<>();
		return mf;
	}

	public HashMap<Integer, FieldParseInfo> getFullSpecMap(String encode) {
		if (fullSpecMap != null) {
			return fullSpecMap;
		}

		fullSpecMap = new HashMap<>();

		fullSpecMap.put(2, FieldParseInfo.getInstance(IsoType.LLVAR, 19, encode));
		fullSpecMap.put(3, FieldParseInfo.getInstance(IsoType.NUMERIC, 6, encode));
		fullSpecMap.put(4, FieldParseInfo.getInstance(IsoType.NUMERIC, 12, encode));
		fullSpecMap.put(5, FieldParseInfo.getInstance(IsoType.NUMERIC, 12, encode));
		fullSpecMap.put(6, FieldParseInfo.getInstance(IsoType.NUMERIC, 12, encode));
		fullSpecMap.put(7, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(8, FieldParseInfo.getInstance(IsoType.NUMERIC, 8, encode));
		fullSpecMap.put(9, FieldParseInfo.getInstance(IsoType.NUMERIC, 8, encode));
		fullSpecMap.put(10, FieldParseInfo.getInstance(IsoType.NUMERIC, 8, encode));
		fullSpecMap.put(11, FieldParseInfo.getInstance(IsoType.NUMERIC, 6, encode));
		fullSpecMap.put(12, FieldParseInfo.getInstance(IsoType.NUMERIC, 6, encode));
		fullSpecMap.put(13, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(14, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(15, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(16, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(17, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(18, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(19, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(20, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(21, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(22, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(23, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(24, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(25, FieldParseInfo.getInstance(IsoType.NUMERIC, 2, encode));
		fullSpecMap.put(26, FieldParseInfo.getInstance(IsoType.NUMERIC, 2, encode));
		fullSpecMap.put(27, FieldParseInfo.getInstance(IsoType.NUMERIC, 1, encode));
		fullSpecMap.put(28, FieldParseInfo.getInstance(IsoType.ALPHA, 8, encode));
		fullSpecMap.put(29, FieldParseInfo.getInstance(IsoType.ALPHA, 8, encode));
		fullSpecMap.put(30, FieldParseInfo.getInstance(IsoType.ALPHA, 8, encode));
		fullSpecMap.put(31, FieldParseInfo.getInstance(IsoType.ALPHA, 8, encode));
		fullSpecMap.put(32, FieldParseInfo.getInstance(IsoType.LLVAR, 11, encode));
		fullSpecMap.put(33, FieldParseInfo.getInstance(IsoType.LLVAR, 11, encode));
		fullSpecMap.put(34, FieldParseInfo.getInstance(IsoType.LLVAR, 28, encode));
		fullSpecMap.put(35, FieldParseInfo.getInstance(IsoType.LLVAR, 37, encode));
		fullSpecMap.put(36, FieldParseInfo.getInstance(IsoType.LLLVAR, 104, encode));
		fullSpecMap.put(37, FieldParseInfo.getInstance(IsoType.ALPHA, 12, encode));
		fullSpecMap.put(38, FieldParseInfo.getInstance(IsoType.ALPHA, 6, encode));
		fullSpecMap.put(39, FieldParseInfo.getInstance(IsoType.ALPHA, 2, encode));
		fullSpecMap.put(40, FieldParseInfo.getInstance(IsoType.ALPHA, 3, encode));
		fullSpecMap.put(41, FieldParseInfo.getInstance(IsoType.ALPHA, 8, encode));
		fullSpecMap.put(42, FieldParseInfo.getInstance(IsoType.ALPHA, 15, encode));
		fullSpecMap.put(43, FieldParseInfo.getInstance(IsoType.ALPHA, 40, encode));
		fullSpecMap.put(44, FieldParseInfo.getInstance(IsoType.LLVAR, 25, encode));
		fullSpecMap.put(45, FieldParseInfo.getInstance(IsoType.LLVAR, 76, encode));
		fullSpecMap.put(46, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(47, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(48, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(49, FieldParseInfo.getInstance(IsoType.ALPHA, 3, encode));
		fullSpecMap.put(50, FieldParseInfo.getInstance(IsoType.ALPHA, 3, encode));
		fullSpecMap.put(51, FieldParseInfo.getInstance(IsoType.ALPHA, 3, encode));
		fullSpecMap.put(52, FieldParseInfo.getInstance(IsoType.BINARY, 64, encode));
		fullSpecMap.put(53, FieldParseInfo.getInstance(IsoType.NUMERIC, 16, encode));
		fullSpecMap.put(54, FieldParseInfo.getInstance(IsoType.LLLVAR, 120, encode));
		fullSpecMap.put(55, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(56, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(57, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(58, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(59, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(60, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(61, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(62, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(63, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(64, FieldParseInfo.getInstance(IsoType.BINARY, 64, encode));
		fullSpecMap.put(65, FieldParseInfo.getInstance(IsoType.BINARY, 1, encode));
		fullSpecMap.put(66, FieldParseInfo.getInstance(IsoType.NUMERIC, 1, encode));
		fullSpecMap.put(67, FieldParseInfo.getInstance(IsoType.NUMERIC, 2, encode));
		fullSpecMap.put(68, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(69, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(70, FieldParseInfo.getInstance(IsoType.NUMERIC, 3, encode));
		fullSpecMap.put(71, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(72, FieldParseInfo.getInstance(IsoType.NUMERIC, 4, encode));
		fullSpecMap.put(73, FieldParseInfo.getInstance(IsoType.NUMERIC, 6, encode));
		fullSpecMap.put(74, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(75, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(76, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(77, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(78, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(79, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(80, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(81, FieldParseInfo.getInstance(IsoType.NUMERIC, 10, encode));
		fullSpecMap.put(82, FieldParseInfo.getInstance(IsoType.NUMERIC, 12, encode));
		fullSpecMap.put(83, FieldParseInfo.getInstance(IsoType.NUMERIC, 12, encode));
		fullSpecMap.put(84, FieldParseInfo.getInstance(IsoType.NUMERIC, 12, encode));
		fullSpecMap.put(85, FieldParseInfo.getInstance(IsoType.NUMERIC, 12, encode));
		fullSpecMap.put(86, FieldParseInfo.getInstance(IsoType.NUMERIC, 16, encode));
		fullSpecMap.put(87, FieldParseInfo.getInstance(IsoType.NUMERIC, 16, encode));
		fullSpecMap.put(88, FieldParseInfo.getInstance(IsoType.NUMERIC, 16, encode));
		fullSpecMap.put(89, FieldParseInfo.getInstance(IsoType.NUMERIC, 16, encode));
		fullSpecMap.put(90, FieldParseInfo.getInstance(IsoType.NUMERIC, 42, encode));
		fullSpecMap.put(91, FieldParseInfo.getInstance(IsoType.ALPHA, 1, encode));
		fullSpecMap.put(92, FieldParseInfo.getInstance(IsoType.ALPHA, 2, encode));
		fullSpecMap.put(93, FieldParseInfo.getInstance(IsoType.ALPHA, 5, encode));
		fullSpecMap.put(94, FieldParseInfo.getInstance(IsoType.ALPHA, 7, encode));
		fullSpecMap.put(95, FieldParseInfo.getInstance(IsoType.ALPHA, 42, encode));
		fullSpecMap.put(96, FieldParseInfo.getInstance(IsoType.BINARY, 64, encode));
		fullSpecMap.put(97, FieldParseInfo.getInstance(IsoType.ALPHA, 16, encode));
		fullSpecMap.put(98, FieldParseInfo.getInstance(IsoType.ALPHA, 25, encode));
		fullSpecMap.put(99, FieldParseInfo.getInstance(IsoType.LLVAR, 11, encode));
		fullSpecMap.put(100, FieldParseInfo.getInstance(IsoType.LLVAR, 11, encode));
		fullSpecMap.put(101, FieldParseInfo.getInstance(IsoType.LLVAR, 17, encode));
		fullSpecMap.put(102, FieldParseInfo.getInstance(IsoType.LLVAR, 28, encode));
		fullSpecMap.put(103, FieldParseInfo.getInstance(IsoType.LLVAR, 28, encode));
		fullSpecMap.put(104, FieldParseInfo.getInstance(IsoType.LLLVAR, 100, encode));
		fullSpecMap.put(105, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(106, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(107, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(108, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(109, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(110, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(111, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(112, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(113, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(114, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(115, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(116, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(117, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(118, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(119, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(120, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(121, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(122, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(123, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(124, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(125, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(126, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(127, FieldParseInfo.getInstance(IsoType.LLLVAR, 999, encode));
		fullSpecMap.put(128, FieldParseInfo.getInstance(IsoType.BINARY, 64, encode));

		return fullSpecMap;
	}

	public String getFieldName(int key) {
		if (fullFieldNameMap == null) {
			fullFieldNameMap = new HashMap<>();
			fullFieldNameMap.put(1, "Bitmap");
			fullFieldNameMap.put(2, "Primary account number (PAN)");
			fullFieldNameMap.put(3, "Processing Code");
			fullFieldNameMap.put(4, "Amount Transaction");
			fullFieldNameMap.put(5, "Amount, settlement");
			fullFieldNameMap.put(6, "Amount, cardholder billing");
			fullFieldNameMap.put(7, "Transmission date & time");
			fullFieldNameMap.put(8, "Amount, cardholder billing fee");
			fullFieldNameMap.put(9, "Conversion rate, settlement");
			fullFieldNameMap.put(10, "Conversion rate, cardholder billing");
			fullFieldNameMap.put(11, "System trace audit number (STAN)");
			fullFieldNameMap.put(12, "Local transaction time (hhmmss)");
			fullFieldNameMap.put(13, "Local transaction date (MMDD)");
			fullFieldNameMap.put(14, "Expiration date (YYMM)");
			fullFieldNameMap.put(15, "Settlement date");
			fullFieldNameMap.put(16, "Currency conversion date");
			fullFieldNameMap.put(17, "Capture date");
			fullFieldNameMap.put(18, "Merchant type, or merchant category code");
			fullFieldNameMap.put(19, "Acquiring institution (country code)");
			fullFieldNameMap.put(20, "PAN extended (country code)");
			fullFieldNameMap.put(21, "Forwarding institution (country code)");
			fullFieldNameMap.put(22, "Point of service entry mode");
			fullFieldNameMap.put(23, "Application PAN sequence number");
			fullFieldNameMap.put(24, "Function code (ISO 8583:1993)");
			fullFieldNameMap.put(25, "Point of service condition code");
			fullFieldNameMap.put(26, "Point of service capture code");
			fullFieldNameMap.put(27, "Authorizing identification response length");
			fullFieldNameMap.put(28, "Amount, transaction fee");
			fullFieldNameMap.put(29, "Amount, settlement fee");
			fullFieldNameMap.put(30, "Amount, transaction processing fee");
			fullFieldNameMap.put(31, "Amount, settlement processing fee");
			fullFieldNameMap.put(32, "Acquiring institution identification code");
			fullFieldNameMap.put(33, "Forwarding institution identification code");
			fullFieldNameMap.put(34, "Primary account number, extended");
			fullFieldNameMap.put(35, "Track 2 data");
			fullFieldNameMap.put(36, "Track 3 data");
			fullFieldNameMap.put(37, "Retrieval reference number");
			fullFieldNameMap.put(38, "Authorization identification response");
			fullFieldNameMap.put(39, "Response code");
			fullFieldNameMap.put(40, "Service restriction code");
			fullFieldNameMap.put(41, "Card acceptor terminal identification");
			fullFieldNameMap.put(42, "Card acceptor identification code");
			fullFieldNameMap.put(43, "Card acceptor name/location");
			fullFieldNameMap.put(44, "Additional response data");
			fullFieldNameMap.put(45, "Track 1 data");
			fullFieldNameMap.put(46, "Additional data (ISO)");
			fullFieldNameMap.put(47, "Additional data (national)");
			fullFieldNameMap.put(48, "Additional data (private)");
			fullFieldNameMap.put(49, "Currency code, transaction");
			fullFieldNameMap.put(50, "Currency code, settlement");
			fullFieldNameMap.put(51, "Currency code, cardholder billing");
			fullFieldNameMap.put(52, "Personal identification number data");
			fullFieldNameMap.put(53, "Security related control information");
			fullFieldNameMap.put(54, "Additional amounts");
			fullFieldNameMap.put(55, "ICC data – EMV having multiple tags");
			fullFieldNameMap.put(56, "Reserved (ISO)");
			fullFieldNameMap.put(57, "Reserved (national)");
			fullFieldNameMap.put(58, "Reserved (national)");
			fullFieldNameMap.put(59, "Reserved (national)");
			fullFieldNameMap.put(60, "Reserved (national)");
			fullFieldNameMap.put(61, "Reserved (private)");
			fullFieldNameMap.put(62, "Reserved (private)");
			fullFieldNameMap.put(63, "Reserved (private)");
			fullFieldNameMap.put(64, "Message authentication code (MAC)");
			fullFieldNameMap.put(65, "Extended bitmap indicator");
			fullFieldNameMap.put(66, "Settlement code");
			fullFieldNameMap.put(67, "Extended payment code");
			fullFieldNameMap.put(68, "Receiving institution country code");
			fullFieldNameMap.put(69, "Settlement institution country code");
			fullFieldNameMap.put(70, "Network management information code");
			fullFieldNameMap.put(71, "Message number");
			fullFieldNameMap.put(72, "Last message's number");
			fullFieldNameMap.put(73, "Action date (YYMMDD)");
			fullFieldNameMap.put(74, "Number of credits");
			fullFieldNameMap.put(75, "Credits, reversal number");
			fullFieldNameMap.put(76, "Number of debits");
			fullFieldNameMap.put(77, "Debits, reversal number");
			fullFieldNameMap.put(78, "Transfer number");
			fullFieldNameMap.put(79, "Transfer, reversal number");
			fullFieldNameMap.put(80, "Number of inquiries");
			fullFieldNameMap.put(81, "Number of authorizations");
			fullFieldNameMap.put(82, "Credits, processing fee amount");
			fullFieldNameMap.put(83, "Credits, transaction fee amount");
			fullFieldNameMap.put(84, "Debits, processing fee amount");
			fullFieldNameMap.put(85, "Debits, transaction fee amount");
			fullFieldNameMap.put(86, "Total amount of credits");
			fullFieldNameMap.put(87, "Credits, reversal amount");
			fullFieldNameMap.put(88, "Total amount of debits");
			fullFieldNameMap.put(89, "Debits, reversal amount");
			fullFieldNameMap.put(90, "Original data elements");
			fullFieldNameMap.put(91, "File update code");
			fullFieldNameMap.put(92, "File security code");
			fullFieldNameMap.put(93, "Response indicator");
			fullFieldNameMap.put(94, "Service indicator");
			fullFieldNameMap.put(95, "Replacement amounts");
			fullFieldNameMap.put(96, "Message security code");
			fullFieldNameMap.put(97, "Net settlement amount");
			fullFieldNameMap.put(98, "Payee");
			fullFieldNameMap.put(99, "Settlement institution identification code");
			fullFieldNameMap.put(100, "Receiving institution identification code");
			fullFieldNameMap.put(101, "File name");
			fullFieldNameMap.put(102, "Account identification 1");
			fullFieldNameMap.put(103, "Account identification 2");
			fullFieldNameMap.put(104, "Transaction description");
			fullFieldNameMap.put(105, "Reserved for ISO use");
			fullFieldNameMap.put(106, "Reserved for ISO use");
			fullFieldNameMap.put(107, "Reserved for ISO use");
			fullFieldNameMap.put(108, "Reserved for ISO use");
			fullFieldNameMap.put(109, "Reserved for ISO use");
			fullFieldNameMap.put(110, "Reserved for ISO use");
			fullFieldNameMap.put(111, "Reserved for ISO use");
			fullFieldNameMap.put(112, "Reserved for national use");
			fullFieldNameMap.put(113, "Reserved for national use");
			fullFieldNameMap.put(114, "Reserved for national use");
			fullFieldNameMap.put(115, "Reserved for national use");
			fullFieldNameMap.put(116, "Reserved for national use");
			fullFieldNameMap.put(117, "Reserved for national use");
			fullFieldNameMap.put(118, "Reserved for national use");
			fullFieldNameMap.put(119, "Reserved for national use");
			fullFieldNameMap.put(120, "Reserved for private use");
			fullFieldNameMap.put(121, "Reserved for private use");
			fullFieldNameMap.put(122, "Reserved for private use");
			fullFieldNameMap.put(123, "Reserved for private use");
			fullFieldNameMap.put(124, "Reserved for private use");
			fullFieldNameMap.put(125, "Reserved for private use");
			fullFieldNameMap.put(126, "Reserved for private use");
			fullFieldNameMap.put(127, "Reserved for private use");
			fullFieldNameMap.put(128, "Message authentication code");
		}

		return fullFieldNameMap.get(new Integer(key));
	}

	public void assembleDataBytes() {
		try {
			IsoMessage m = assembleToIsoMessage();

			if (logger.isDebugEnabled())
				logger.debug("Generated [" + getLayoutName() + "] ISO Message: \n"
						+ ISO8583DumpUtil.dumpIsoMessage(m, this));

			data = m.writeData();
		} catch (Exception e) {
			throw new MessageException(e.getMessage());
		}
	}

	private IsoMessage assembleToIsoMessage() {
		byte[] messageType = (byte[]) getChildByName(FIELD_NAME_MESSAGE_TYPE).getValue();
		if (messageType == null) {
			messageType = (byte[]) getChildByName(FIELD_NAME_MESSAGE_TYPE).getDefaultValue();
			if (messageType == null) {
				throw new MessageException(
						String.format("[%s.%s] No have DefaultValue.", layout.getName(), FIELD_NAME_MESSAGE_TYPE));
			}
			getChildByName(FIELD_NAME_MESSAGE_TYPE).setValue(messageType);
		}

		MessageFactory<IsoMessage> mf = createMessageFactory();
		IsoMessage m = mf.newMessage(Integer.parseInt(new String(messageType), 16));
		for (int i = 2; i <= 128; i++) {
			Item item = getChildByName(String.format("%03d", i));
			if (item == null) {
				continue;
			}

			IsoValue<?> isoValue = getFieldFromLayout(item);
			if (isoValue != null) {
				m.putAt(i, isoValue);
			}
		}
		return m;
	}

	protected IsoValue<?> getFieldFromLayout(Item item) {
		if ("exclude".equals(item.getDataTypeRef())) {
			return null;
		}

		int length = item.getLength();
		IsoType isoType = IsoType.valueOf(item.getDataTypeRef());
		IsoValue<?> isoValue = null;
		if (item.getNodeTypeId() == NODE_GROUP) {
			isoValue = getCompositeFieldFromLayout(item);
		} else {
			if (item.getValue() != null) {
				String rtnValue = new String((byte[]) item.getValue());
				isoValue = isoType.needsLength() ? new IsoValue<>(isoType, rtnValue, length)
						: new IsoValue<>(isoType, rtnValue);
			}
		}

		return isoValue;
	}

	private IsoValue<?> getCompositeFieldFromLayout(Item item) {
		List<Item> list = item.getChildList();
		if (list == null || list.size() == 0) {
			return null;
		}

		IsoType isoType = IsoType.valueOf(item.getDataTypeRef());
		CompositeField cf = new CompositeField();
		for (Item subItem : list) {
			if (subItem.getIndex() > -1) {
				IsoValue<?> isoValue = null;
				if (subItem.getNodeTypeId() == NODE_FIELD) {
					isoValue = getFieldFromLayout(subItem);
				} else if (subItem.getNodeTypeId() == NODE_GROUP) {
					isoValue = getCompositeFieldFromLayout(subItem);
				}

				if (isoValue != null) {
					cf.addValue(isoValue);
				}
			}
		}

		if (cf.getValues() == null) {
			return null;
		}

		return isoType.needsLength() ? new IsoValue<>(isoType, cf, length, cf) : new IsoValue<>(isoType, cf, cf);
	}

	protected void assignRepresentativeItemValue(byte[] data, IsoMessage m, int isoHeaderLength) {
		Item messageTypeItem = getChildByName(FIELD_NAME_MESSAGE_TYPE);
		if (messageTypeItem != null) {
			// messageTypeItem.setValue(new String(Arrays.copyOfRange(data, 0, 4)));
			messageTypeItem.setValue(String.format("%04x", m.getType()));
			if (logger.isDebugEnabled()) {
				logger.debug("messageType=" + String.format("%04x", m.getType()));
			}
		}

		Item primaryBitMapItem = getChildByName(FIELD_NAME_PRIMARY_BITMAP);
		if (primaryBitMapItem != null) {
			int startIdx = isoHeaderLength + 4;
			if (m.isBinaryHeader()) {
				startIdx = isoHeaderLength + 2;
			}

			if (m.isBinaryBitmap()) {
				byte[] primaryBitMapArr = Arrays.copyOfRange(data, startIdx, startIdx + 8);
				primaryBitMapItem.setValue(primaryBitMapArr);
				boolean hasSecondaryBitMap = false;
				for (int i = 64; i < 129; i++) {
					if (m.hasField(i)) {
						hasSecondaryBitMap = true;
						break;
					}
				}
				if (hasSecondaryBitMap) {
					Item secondaryBitMapItem = getChildByName("001");
					if (secondaryBitMapItem != null) {
						secondaryBitMapItem.setValue(Arrays.copyOfRange(data, startIdx + 8, startIdx + 8 + 8));
					}
				}
			} else {
				byte[] primaryBitMapArr = Arrays.copyOfRange(data, startIdx, startIdx + 16);
				primaryBitMapItem.setValue(new String(primaryBitMapArr));
				boolean hasSecondaryBitMap = false;
				for (int i = 64; i < 129; i++) {
					if (m.hasField(i)) {
						hasSecondaryBitMap = true;
						break;
					}
				}
				if (hasSecondaryBitMap) {
					Item secondaryBitMapItem = getChildByName("001");
					if (secondaryBitMapItem != null) {
						secondaryBitMapItem
								.setValue(new String(Arrays.copyOfRange(data, startIdx + 16, startIdx + 16 + 16)));
					}
				}
			}
		}
	}

	protected void assignItemValue(int num, IsoMessage m) {
		String itemName = String.format("%03d", num);
		Item item = getChildByName(itemName);

		if (item == null)
			return;

		switch (item.getNodeTypeId()) {
		case NODE_FIELD:
			item.setValue(m.getField(num).toString());
			break;
		case NODE_GROUP:
			CompositeField cf = m.getObjectValue(num);
			assignCompositeItemValue(item, cf, itemName);
			break;
		}
	}

	private void assignCompositeItemValue(Item item, CompositeField cf, String itemName) {
		if (cf == null || cf.getValues() == null) {
			return;
		}

		for (int i = 0; i < cf.getValues().size(); i++) {
			String subItemName = String.format("%s:%d", itemName, i + 1);
			Item subItem = item.getChildByName(subItemName + "[0]");
			if (subItem != null) {
				if (subItem.getNodeTypeId() == NODE_FIELD) {
					subItem.setValue(cf.getField(i).toString());
				} else if (subItem.getNodeTypeId() == NODE_GROUP) {
					CompositeField f = cf.getObjectValue(i);
					assignCompositeItemValue(subItem, f, subItemName);
				}
			}
		}
	}

	/**
	 * 레이아웃에 정의 된 SPEC을 기준으로 ParseMap을 만드는 메소드<br>
	 * 데이터 byte[]에서 MessageType을 추출하여 해당 Map을 셋팅한다
	 * 
	 * @param buf      데이터
	 * @param mf       j8583 Message Factory
	 * @param layout   레이아웃 객체
	 * @param itemsMap message 객체 필드 정보 Item 맵
	 * @throws NumberFormatException
	 * @throws UnsupportedEncodingException
	 */
	@SuppressWarnings("unchecked")
	public <T extends IsoMessage> void setParseMapFromLayout(byte[] buf, MessageFactory<T> mf, Layout layout,
			Map<String, Item> itemsMap, int isoHeaderLength) throws Exception {
		HashMap<Integer, FieldParseInfo> parseMap = new HashMap<>();
		int type = getIsoMessageType((MessageFactory<IsoMessage>) mf, buf, isoHeaderLength);

		for (int i = 2; i <= 128; i++) {
			Item item = itemsMap.get(String.format("%03d", i));
			if (item != null) {
				parseMap.put(i, getParserFromLayout(item, mf));
			}
		}
		mf.setParseMap(type, parseMap);
	}

	protected <T extends IsoMessage> FieldParseInfo getParserFromLayout(Item item, MessageFactory<T> mfact) {
		IsoType itype = IsoType.valueOf(item.getDataTypeRef());
		FieldParseInfo fpi = FieldParseInfo.getInstance(itype, item.getLength(), mfact.getCharacterEncoding());

		if (item.getNodeTypeId() == NODE_GROUP) {
			List<Item> list = item.getChildList();
			if (list != null && list.size() > 0) {
				CompositeField cf = new CompositeField();
				for (Item subItem : list) {
					if (subItem.getIndex() > -1) {
						cf.addParser(getParserFromLayout(subItem, mfact));
					}
				}

				if (cf.getParsers() != null) {
					fpi.setDecoder(cf);
				}
			}
		}

		return fpi;
	}

	public IsoMessage parseIsoMessage(byte[] message, int isoHeaderLength) throws Exception {
		MessageFactory<IsoMessage> mf = createMessageFactory();
		int messageType = getIsoMessageType(mf, message, isoHeaderLength);
		mf.setParseMap(messageType, getFullSpecMap(mf.getCharacterEncoding()));
		return mf.parseMessage(message, isoHeaderLength, mf.isBinaryHeader());
	}

	protected int getIsoMessageType(MessageFactory<IsoMessage> mf, byte[] buf, int isoHeaderLength) throws Exception {
		int messageType;

		if (mf.isBinaryHeader()) {
			messageType = ((buf[isoHeaderLength] & 0xff) << 8) | (buf[isoHeaderLength + 1] & 0xff);
		} else if (mf.isForceStringEncoding()) {
			messageType = Integer.parseInt(new String(buf, isoHeaderLength, 4, mf.getCharacterEncoding()), 16);
		} else {
			messageType = ((buf[isoHeaderLength] - 48) << 12) | ((buf[isoHeaderLength + 1] - 48) << 8)
					| ((buf[isoHeaderLength + 2] - 48) << 4) | (buf[isoHeaderLength + 3] - 48);
		}

		return messageType;
	}

	protected int getIsoHeaderLength() {
		return this.isoHeaderLength;
	}

	protected void setIsoHeaderLength(int isoHeaderLength) {
		this.isoHeaderLength = isoHeaderLength;
	}

	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(
				"[ISO8583 message]");
		sb.append("\n" + super.toString());
		sb.append(
				"\n");
		return sb.toString();
	}
}