package nlib.restful.service;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import nlib.util.StringUtil;

public class XmlConverter {
	
	private static final Logger log = LoggerFactory.getLogger(XmlConverter.class);

	public static HashMap<String, Object> convert(String xmlStr, String rootEleName) throws Exception {
		
		if(xmlStr == null) return null;
		
		ByteArrayInputStream bIn = new ByteArrayInputStream(xmlStr.getBytes());
		HashMap<String, Object> resMap = new HashMap<String, Object>();

		try {
			XMLInputFactory inputFactory = XMLInputFactory.newInstance();
			XMLEventReader eventReader = inputFactory.createXMLEventReader(bIn);

			resMap = getElementValue(rootEleName, eventReader, null);
			//log.debug("Converting XML to HashMap : Done >> " + resMap);
		} catch (XMLStreamException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return resMap;
	}
	
	/**
	 * @param rootElementName
	 * @param eventReader
	 * @param alreadyRootOpened : 부모 요소 찾기 없이 작업 진행할지 여부. false인 경우, rootElementName 요소를 찾아서 이후 값 추출 수행 
	 * @param startElement
	 * @return
	 * @throws Exception
	 */
	public static HashMap<String, Object> getElementValue(
			  String rootElementName
			, XMLEventReader eventReader
			, XMLEvent firstChildElement) throws Exception {

		HashMap<String, Object> retMap = new HashMap<String, Object>();
		
		try {
			
			int depth = (firstChildElement == null ? 0 : 1);
			StartElement startElement = null;

			XMLEvent event = firstChildElement;
			
			String upperElementName = null;
			String elementName = null; //(firstChildElement == null ? null : firstChildElement.asStartElement().getName().getLocalPart());
			String content = null;
			String elementValue = "";
			HashMap<String, String> elementValues = null;
			boolean hasSubElements = false;
			boolean isFirst = true;
			
			while (eventReader.hasNext()) {
				
				// 이벤트가 함께 매개변수로 넘어온 경우, 처음 읽기 생략 
				if(!isFirst || event == null) {
					event = eventReader.nextEvent();
				}
				
				if(isFirst) isFirst = false;
				
				
				// 1 레벨 엘리먼트 찾기 
				if (depth == 0 && event.isStartElement() && event.asStartElement().getName().getLocalPart().equals(rootElementName)) {
					depth++;
					continue;
				}
				
				// 1 레벨 엘리먼트내의 2 레벨 엘리먼트 찾기  
				if(depth == 1 && elementName == null && event.isStartElement()) {
					elementName = event.asStartElement().getName().getLocalPart();
					elementValue = "";
					elementValues = null;
					hasSubElements = false;
					depth++;
					continue;
				}
				
				// 2 레벨 엘리먼트 내용 찾기
				if(depth == 2 && elementName != null && event.isCharacters()) {
					content = event.asCharacters().getData();
					if(StringUtil.isNotEmpty(content)) {
						elementValue += content;
					}
					continue;
				}
				
				// 2 레벨 엘리먼트 종료
				if(depth == 2 && elementName != null && event.isEndElement()) {
					
					if(!elementName.equals(event.asEndElement().getName().getLocalPart())) {
						throw new Exception("XML문의 요소 시작/종료 태그가 일치하지 않습니다. : " + elementName + " 요소가 " + event.asEndElement().getName().getLocalPart() + "로 종료되었습니다.");
					}
					
					addValue(retMap, elementName, elementValue);
					//log.debug(elementName + " : -" + elementValue + "_");
					
					elementName = null;
					elementValue = "";
					depth--;
				}

				// 2 레벨 엘리먼트 > 하위 엘리먼트 존재하는 경우 
				if(depth == 2 && elementName != null && event.isStartElement()) {
					HashMap<String, Object> subMap = getElementValue(elementName, eventReader, event);
					addValue(retMap, elementName, subMap);
					depth--;
					elementName = null;
				}
				
				// 1 레벨 엘리먼트 종료된 경우, 돌아가기 
				if(depth == 1 && elementName == null && event.isEndElement() && rootElementName.equals(event.asEndElement().getName().getLocalPart())) {
					depth--;
					return retMap;
				}
				
			} // while 
			
		} catch (XMLStreamException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return retMap;
	}
	
	public static void addValue(HashMap<String, Object> map, String key, Object value) throws Exception {
		if(map == null) throw new Exception("값을 넣을 Map 변수가 초기화되지 않았습니다.");
		
		if(key == null) return;
		if(value == null) return;
		
		Object curValObj = map.get(key);
		if(curValObj == null) {
			map.put(key, value);
			return;
		}
		
		if(curValObj instanceof String || curValObj instanceof HashMap) {
			List<Object> values = new ArrayList<Object>();
			values.add(curValObj);
			values.add(value);
			map.put(key, values);
		} else if(curValObj instanceof List) {
			((ArrayList<Object>)curValObj).add(value);
		}
		
		return;
	}
	
	public static Object convertMapToObject(HashMap<String, String> map, Class voClass) {
		final ObjectMapper mapper = new ObjectMapper(); // jackson's objectmapper
		mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
		final Object voObj = mapper.convertValue(map, voClass);
		return voObj;
	}
	
/*
	public static void main(String argv[]) throws Exception {

		XmlConverter xmlConverter = new XmlConverter();
		String xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n" + 
				"<!-- uac.service.board.qna_GET.xml -->\r\n" + 
				"<result>\r\n" + 
				"    <code>S</code>\r\n" + 
				"    <message>성공적으로 조회되었습니다.</message>\r\n" + 
				"    <records>10</records>\r\n" + 
				"	<page>1</page>\r\n" + 
				"	<totPages>15</totPages>\r\n" + 
				"    <record>\r\n" + 
				"        <noticeNo>1</noticeNo>\r\n" + 
				"        <noticeType>101</noticeType>\r\n" + 
				"        <title>공지사항 테스트</title>\r\n" + 
				"        <readCount>15</readCount>\r\n" + 
				"        <regDate>2021-07-19</regDate>\r\n" + 
				"    </record>\r\n" + 
				"    <record>\r\n" + 
				"        <noticeNo>2</noticeNo>\r\n" + 
				"        <noticeType>101</noticeType>\r\n" + 
				"        <title>공지사항 테스트</title>\r\n" + 
				"        <readCount>55</readCount>\r\n" + 
				"        <regDate>2021-07-19</regDate>\r\n" + 
				"    </record>\r\n" + 
				"    <record>\r\n" + 
				"        <noticeNo>3</noticeNo>\r\n" + 
				"        <noticeType>101</noticeType>\r\n" + 
				"        <title>공지사항 테스트</title>\r\n" + 
				"        <readCount>55</readCount>\r\n" + 
				"        <regDate>2021-07-19</regDate>\r\n" + 
				"    </record>\r\n" + 
				"    <record>\r\n" + 
				"        <noticeNo>4</noticeNo>\r\n" + 
				"        <noticeType>101</noticeType>\r\n" + 
				"        <title>공지사항 테스트</title>\r\n" + 
				"        <readCount>55</readCount>\r\n" + 
				"        <regDate>2021-07-19</regDate>\r\n" + 
				"    </record>\r\n" + 
				"    <record>\r\n" + 
				"        <noticeNo>5</noticeNo>\r\n" + 
				"        <noticeType>101</noticeType>\r\n" + 
				"        <title>공지사항 테스트</title>\r\n" + 
				"        <readCount>55</readCount>\r\n" + 
				"        <regDate>2021-07-19</regDate>\r\n" + 
				"    </record>\r\n" + 
				"    <record>\r\n" + 
				"        <noticeNo>6</noticeNo>\r\n" + 
				"        <noticeType>101</noticeType>\r\n" + 
				"        <title>공지사항 테스트</title>\r\n" + 
				"        <readCount>55</readCount>\r\n" + 
				"        <regDate>2021-07-19</regDate>\r\n" + 
				"    </record>\r\n" + 
				"	<end>3333</end>\r\n" + 
				"</result>"; 
		
		HashMap<String, Object> resMap = xmlConverter.convert(xmlStr);
	}
	*/
}
