package com.eactive.eai.transformer.dao.excel;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.eactive.eai.data.entity.onl.transformer.layout.LayoutEntity;
import com.eactive.eai.transformer.dao.TransformerDaoException;
import com.eactive.eai.transformer.layout.Item;
import com.eactive.eai.transformer.layout.ItemFactory;
import com.eactive.eai.transformer.layout.Layout;
import com.eactive.eai.transformer.layout.LayoutManager;
import com.eactive.eai.transformer.layout.LayoutType;
import com.eactive.eai.transformer.layout.LayoutTypeManager;
import com.eactive.eai.transformer.layout.LayoutValidator;
import com.eactive.eai.transformer.layout.loader.LayoutLoader;
import com.eactive.eai.transformer.layout.mapper.LayoutMapper;
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.ConstantUtil;

@Service
public class LayoutExcelDao extends BaseExcelDao<String, Layout> {
	private static final int DATA_ROW_OFFSET = 10;
	private static final int COL_NO = 0;
	private static final int COL_NODE = 1;
	private static final int COL_DPTH = 2;
	private static final int COL_NAME = 3;
	private static final int COL_DESC = 4;
	private static final int COL_TYPE = 5;
	private static final int COL_LEN = 6;
	private static final int COL_PATH = 7;
	private static final int COL_OCC_TYPE = 8;
	private static final int COL_OCC_MIN = 9;
	private static final int COL_DEF_VAL = 10;
	private static final int COL_REF = 11;
	private static final int COL_OCC_REF = 12;
	private static final int COL_MASK = 13;
	private static final int COL_LAYOUT_TYPE_COL = 2;
	private static final int COL_LAYOUT_TYPE_ROW = 8;
	private static final int COL_DPLEN = 14;

	@Autowired
	private LayoutTypeManager layoutTypeManager;

	@Autowired
	private LayoutLoader layoutLoader;

	@Autowired
	private LayoutMapper layoutMapper;

	public LayoutExcelDao() {
		super("layout");
	}

	public Layout findLayoutByName(String layoutName) {
		return null;
	}

	public Item findItemByName(String layoutName) {
		Layout l = LayoutManager.getManager().getLayoutByName(layoutName);
		if (l != null) {
			return l.getRootItem();
		} else {
			return null;
		}
	}

	protected Map<String, Layout> loadSheets(Sheet[] sheets) {
		Map<String, Layout> map = parse(sheets);
		return map;
	}

	protected Map<String, Layout> removeSheets(Sheet[] sheets) throws Exception {

		Item rootItem = (Item) parse(sheets);
		try {
			layoutLoader.deleteById(rootItem.getName());
		} catch (Exception e) {
			throw e;
		}

		return null;
	}

	protected Map<String, Layout> parse(Sheet[] sheets) {
		try {
			Sheet sheet = sheets[0];

			/**
			 * layout type
			 */
			String layoutTypeName = sheet.getCellString(COL_LAYOUT_TYPE_ROW, COL_LAYOUT_TYPE_COL).trim();

			LayoutType layoutType = layoutTypeManager.getLayoutType(layoutTypeName);
			if (layoutType == null) {
				throw new TransformerDaoException("LayoutTypeManager에서 LoutPtrnName을 찾을 수 없습니다. - " + layoutTypeName);
			}

			/**
			 * build item tree
			 */
			int rownum = DATA_ROW_OFFSET;
			Item top = makeItem(sheet, layoutTypeName, rownum++);
			String layoutName = top.getName();
			top.setLayoutName(layoutName);
			top = buildTree(layoutName, top, sheet, layoutTypeName, rownum);
			top.initItemParentId(0, 0);

			/**
			 * layout
			 */
			Layout layout = new Layout();
			layout.setName(top.getName());
			layout.setDescription(top.getDescription());
			// layout.setBzwkDstcd(???);
			layout.setLastUpdateDate(LocalDateTime.now());

			layout.setRootItem(top);
			((Message) top).setLayout(layout);
			layout.setLayoutType(layoutType);

			/**
			 * set overwrite
			 */
			boolean overwrite = sheet.isEquals(6, 4, "O");
			layout.setOverwrite(overwrite);

			/**
			 * map
			 */
			Map<String, Layout> treeMap = new TreeMap<>();
			treeMap.put(layout.getName(), layout);
			return treeMap;
		} catch (Exception e) {
			throw new TransformerDaoException(e.getMessage(), e);
		}
	}

	private Item buildTree(String layoutName, Item parent, Sheet sheet, String messageType, int rownum)
			throws Exception {
		Item item = makeItem(sheet, messageType, rownum);
		Item next = makeItem(sheet, messageType, rownum + 1);
		item.setLayoutName(layoutName);

		if (!sheet.existsRow(rownum)) {
			return parent;
		} else if (item.getDepth() > parent.getDepth()) {
			try {
				parent.addChild(item);
			} catch (UnsupportedOperationException e) {
				StringBuffer sb = new StringBuffer();
				sb.append(layoutName);
				sb.append(" | cannot add item to parent - ");
				sb.append("parent=(" + parent.getNodeType() + ", " + parent.getName() + ", " + parent.getDepth() + ")");
				sb.append(", item=(" + item.getNodeType() + ", " + item.getName() + ", " + item.getDepth() + ")");
				throw new Exception(sb.toString(), e);
			}

			item.setParent(parent);

			if (next == null) {
			} else if (next.getDepth() > item.getDepth()) {
				item = buildTree(layoutName, item, sheet, messageType, ++rownum);
			} else if (next.getDepth() == item.getDepth()) {
				parent = buildTree(layoutName, parent, sheet, messageType, ++rownum);
			} else {
				for (int i = 0; i < item.getDepth() - next.getDepth(); i++)
					parent = parent.getParent();
				parent = buildTree(layoutName, parent, sheet, messageType, ++rownum);
			}
			return parent;
		} else {
			throw new Exception("invalid depth : " + item);
		}
	}

	private Item makeItem(Sheet sheet, String messageType, int rownum) throws Exception {
		if (sheet.getCellValue(rownum, 0) == null || sheet.getCellValue(rownum, 1) == null)
			return null;

		Item item = null;

		try {
			String nodeType = sheet.getCellString(rownum, COL_NODE).trim();
			String name = sheet.getCellString(rownum, COL_NAME).trim();
			String layoutTypeName = sheet.getCellString(COL_LAYOUT_TYPE_ROW, COL_LAYOUT_TYPE_COL).trim();

			if (name.trim().length() <= 0)
				return null;

			if ("MESSAGE".equals(nodeType) || "INCLUDE".equals(nodeType)) {
				item = MessageFactory.getFactory().createMessage(layoutTypeName);
			} else {
				int nodeTypeId = ConstantUtil.getNodeTypeId(sheet.getCellString(rownum, COL_NODE).trim());
				item = ItemFactory.createItem(nodeTypeId);
			}

			item.setName(sheet.getCellString(rownum, COL_NAME).trim());
			if ("MESSAGE".equals(nodeType) || "INCLUDE".equals(nodeType)) {
				item.setLayoutName(item.getName());
			}

			try {
				item.setSeqNo((new Double(sheet.getCellValue(rownum, COL_NO))).intValue());
			} catch (NumberFormatException e) {
				logger.warn("sequence number is invalid value[row=" + rownum + ", col=" + COL_NO + "]='"
						+ sheet.getCellValue(rownum, COL_NO) + "' - " + e.getMessage());
				item.setSeqNo(-1);
			}
			item.setDepth(sheet.getCellInt(rownum, COL_DPTH));
			item.setDescription(sheet.getCellString(rownum, COL_DESC).trim());
			item.setDataTypeRef(sheet.getCellString(rownum, COL_TYPE).trim());
			item.setDataTypeId(ConstantUtil.getDataTypeId(sheet.getCellString(rownum, COL_TYPE).trim(), messageType));
			item.setPath(sheet.getCellString(rownum, COL_PATH).trim());

			try {
				item.setLength(sheet.getCellInt(rownum, COL_LEN));
			} catch (Exception e) {
				throw new RuntimeException("Length 값이 잘못 설정되었습니다. - " + sheet.getCellString(rownum, COL_LEN).trim(), e);
			}

			try {
				item.setDecimalPointLength(sheet.getCellInt(rownum, COL_DPLEN));
			} catch (Exception e) {
				item.setDecimalPointLength(0);
			}

			item.setRefInfo(sheet.getCellString(rownum, COL_REF).trim());

			/**
			 * log masking
			 */
			if (sheet.getCellString(rownum, COL_MASK).trim().matches("(o|O|0|○|y|Y)")) {
				item.setMask("Y");
			}

			item.setOccRef(sheet.getCellString(rownum, COL_OCC_REF).trim());

			String occType = sheet.getCellString(rownum, COL_OCC_TYPE).trim();
			item.setOccType(occType);
			Matcher m = Pattern.compile("([0-9]+)\\.\\.([0-9]*)").matcher(occType);
			if (m.find()) {
				item.setOccMin(Integer.parseInt(m.group(1)));
				if ("".equals(m.group(2))) {
					item.setOccMax(Integer.MAX_VALUE);
				} else {
					item.setOccMax(Integer.parseInt(m.group(2)));
				}
			} else if (("?".equals(occType.trim()))) {
				item.setOccMin(sheet.getCellInt(rownum, COL_OCC_MIN));
				item.setOccMax(1);
			} else if (("*".equals(occType.trim()))) {
				item.setOccMin(sheet.getCellInt(rownum, COL_OCC_MIN));
				item.setOccMax(-1);
			} else if (("+".equals(occType.trim()))) {
				item.setOccMin(sheet.getCellInt(rownum, COL_OCC_MIN));
				item.setOccMax(-1);
			} else if (occType.trim().matches("[0-9]+")) {
				item.setOccMin(sheet.getCellInt(rownum, COL_OCC_MIN));
				item.setOccMax(Integer.parseInt(occType.trim()));
			} else {
				throw new MessageException("발생유형 값이 잘못 설정되었습니다. - '" + occType + "'");
			}

			item.setDefaultValue(sheet.getCellString(rownum, COL_DEF_VAL).trim());

			return item;
		} catch (Exception e) {
			throw new Exception("error when read item : " + (item == null ? "" : item.getName()), e);
		}
	}

	public void save(Map<String, Layout> layoutMap) {
		layoutMap.values().stream().forEach(this::saveLayout);
	}

	public void saveLayout(final Layout layout) {

		if (layout.getName() == null) {
			throw new TransformerDaoException("layout name cannot be null");
		} else if (layout.getDescription() == null) {
			throw new TransformerDaoException("layout descriptions cannot be null");
		} else if (layout.getLayoutType().getName() == null) {
			throw new TransformerDaoException("layout type name cannot be null");
		}

		if (logger.isInfoEnabled()) {
			logger.info(String.format("    + %-25s | %s (%s)", "save layout", (layout == null ? "" : layout.getName()),
					(layout == null ? "" : layout.getLayoutType().getName())));
		}

		LayoutValidator.validate(layout);

		/**
		 * overwrite?
		 */
		if (layout.getOverwrite()) {
			layoutLoader.findById(layout.getName()).ifPresent(layoutLoader::delete);
		}

		LayoutEntity layoutEntity = layoutMapper.toEntity(layout);
		layoutEntity.setUseyn("1");

		layoutLoader.save(layoutEntity);
	}

}
