package com.dbs.mplus.service.api.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.dbs.mplus.framework.FrameworkConstants;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
 * JSONObject를 매번 까보는 대신에 공통적으로 필요한 부분을 꺼내기 위함 
 * 경대병원 메시지 구조는 4209 일감 참고 
 * 
 * @author aple
 *
 */
public class JSONResParser {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	// 껍데기 
	private static final String KEY_RESULTINFO = "resultinfo"; 
	// 결과가 들어있는 곳 
	private static final String KEY_RESULT = "result"; 
	// 추가 메시지가 들어있는 key 
	private static final String KEY_EXTRA_MESSAGE = "etcmsg"; 
	// 추가 메시지에서 꺼내올 실제 메시지 
	private static final String KEY_EXTRA_MESSAGE_DATA = "errormsg"; 
	// 기간계에서 error 라고 뱉었는지 여부를 확인하기 위한 key 값 
	private static final String KEY_RESULT_MESSAGE_TYPE = "type"; 
	// 기간계의 에러표시 
	private static final String KEY_RESULT_MESSAGE_TYPE_ERROR = "error"; 
	private static final String KEY_RESULT_MESSAGE_TYPE_ERROR_DESCRIPTION = "description"; 
	// 
	private boolean bError; 
	private boolean bList; 
	private boolean bEmptyResult; 
	private Map<String, Object> resultMap; 
	private List<Map<String, Object>> resultList; 
	private String errorMessage; 
	private String extraMessage; 
	
	/**
	 * 생성자  
	 * @param obj
	 */
	public JSONResParser(JSONObject obj) { 		
//		// ----		
//		this.logger.debug("obj : " + obj.toString());
//		// ---- 
	
		// 응답 결과가 비어 있는지 검사 
		this.bEmptyResult = this.checkEmptyResult(obj); 
		// 결과가 비어있다면 
		if(this.bEmptyResult) {
			// 결과가 에러인지 체크해두고 
			this.bError = this.checkError(obj.getJSONArray(KEY_RESULTINFO)); 
			// 메시지가 에러이면 더 이상 분석하지 않음 
			return; 
		}
		
		// 결과가 있다면, 에러는 아님 
		this.bError = false; 
		
		
		// 응답 결과가 list인지 여부 확인 
		this.bList = this.checkListResultType(obj.getJSONObject(KEY_RESULTINFO)); 
		// 결과가 map인 경우 
		if(!this.bList) { 
			final JSONObject resultObj = obj.getJSONObject(KEY_RESULTINFO).getJSONObject(KEY_RESULT); 
			final JSONObject etcObj = obj.getJSONObject(KEY_RESULTINFO).getJSONObject(KEY_EXTRA_MESSAGE); 

			//
			// 데이터가 있는 경우에만 변환 작업 
			if(resultObj != null && !resultObj.isNullObject()) { 
				this.resultMap = this.convert(resultObj); 
			}
			
			// 추가 메시지가 있으면 해당 메시지를 출력함 
			if(etcObj != null && !etcObj.isNullObject()) {
				this.extraMessage = etcObj.getString(KEY_EXTRA_MESSAGE_DATA); 
			}
			
			// 데이터도 없고 추가 메시지도 없으면 이상한 것임 
			if((this.resultMap == null || this.resultMap.isEmpty()) && StringUtils.isEmpty(this.extraMessage)) {
				this.extraMessage = "기간계에서 메시지를 안 줘서 보여줄 메시지가 없습니다."; 
			}
		}
		else {
			this.resultList = new ArrayList<Map<String, Object>>(); 
			final JSONArray array = obj.getJSONObject(KEY_RESULTINFO).getJSONArray(KEY_RESULT); 
			final JSONObject etcObj = obj.getJSONObject(KEY_RESULTINFO).getJSONObject(KEY_EXTRA_MESSAGE); 
			
			// 추가 메시지가 있으면 해당 메시지를 출력함 
			if(etcObj != null && !etcObj.isNullObject()) {
				this.extraMessage = etcObj.getString(KEY_EXTRA_MESSAGE_DATA); 
			}			
			//
			// 데이터가 있는 경우에만 변환작업  
			if(array != null && !array.isEmpty()) { 
				for(int i = 0; i < array.size(); i++) {
					this.resultList.add(this.convert(array.getJSONObject(i))); 
				}				
			}
			
			// 데이터도 없고, 추가 메시지도 없으면 비정상임 
			if((this.resultList == null || this.resultList.isEmpty()) && StringUtils.isEmpty(this.extraMessage)) { 
				this.extraMessage = "기간계에서 메시지를 안 줘서 보여줄 메시지가 없습니다."; 
			}
		}
	

		// --- 디버깅용 
		if(this.logger.isDebugEnabled()) { 
			final StringBuilder builder = new StringBuilder(); 
			
			builder.append(FrameworkConstants.NEW_LINE); 
			builder.append("기간계에서 받아서 분석한 메시지 ==========").append(FrameworkConstants.NEW_LINE); 
			// 원문을 찍어볼려면 obj객체를 통으로 찍으면됨. 근데 로그가 너무 많음. 
			builder.append(this.toString()); 			
			
			
			this.logger.debug(builder.toString());
		} 
		// -----------		
	}
	
	/**
	 * 결과가 없는 응답인가? 
	 * @param obj
	 * @return
	 */
	private boolean checkEmptyResult(JSONObject obj) { 
		final Object resultInfoObj = obj.get(KEY_RESULTINFO); 
		
		if(resultInfoObj instanceof JSONArray) { 
			return true; 
		}
		
		return false; 
	}
	
	/**
	 * 에러여부 
	 * @param resultInfo
	 * @return
	 */
	private boolean checkError(JSONArray resultInfo) {
		// 일단 길이가 1이라야 함 
		if(resultInfo.size() != 1) {
			if(this.logger.isErrorEnabled()) {
				this.logger.error("예상하지 못한 메시지 구조 : " + resultInfo.toString());
			}
			
			return false; 
		}
		
		// 유일하게 들어있는 정보를 가져와서 
		final JSONObject msgObj = resultInfo.getJSONObject(0); 
		// 시스템이 에러라고 뱉었는지 여부 
		final String type = msgObj.getString(KEY_RESULT_MESSAGE_TYPE); 
		// 시스템이 에러라고 명시적으로 이야기 했나? 
		if(KEY_RESULT_MESSAGE_TYPE_ERROR.equalsIgnoreCase(type)) {
			// 기간계 에러 로그 뽑아서 저장해두고 
			this.errorMessage = msgObj.getString(KEY_RESULT_MESSAGE_TYPE_ERROR_DESCRIPTION);
			
			if(StringUtils.isEmpty(this.errorMessage)) {
				// description에 메시지 구분자로 | 이 들어있음 
				this.errorMessage = this.errorMessage.replaceAll("\\|", FrameworkConstants.NEW_LINE); 
			}
			
			return true; 
		}
		
		return false; 
	}	
	
	/**
	 * 병원에서 응답 받은 결과가 list인지 여부  
	 * 
	 * 이렇게만 체크해서 최종 결과가 list인지 여부를 체크하는 것은 위험함. 
	 * 왜냐면 병원에서 결과를 list로 준다해놓고, 결과가 1건인 경우는 map으로 던지기도 하기 때문임 
	 * 결과가 list인지, map인지에 대한 것은 api 정의에 있는 것이 최종 형상임 
	 * 
	 * 이 메소드는 병원에서 응답 받은 결과가 list 형태인지 여부만 반환하는 메소드임 
	 * 
	 * @param obj
	 * @return
	 */
	private boolean checkListResultType(JSONObject obj) { 
		final Object resultObj = obj.get(KEY_RESULT); 
		
		if(resultObj instanceof JSONArray) { 
			return true; 
		}
	
		return false; 
	}
	
	
	/**
	 * 결과가 에러인지 여부 
	 * @return
	 */
	public boolean isError() { 
		return this.bError; 
	}
	/**
	 * 결과가 리스트인지 여부 
	 * @return
	 */
	public boolean isResultList() { 
		return this.bList; 
	}
	
	/**
	 * isResultList == true 이면 List<Map<String, String>> 반환 
	 * false이면 Map<String, String> 반환 
	 * @return
	 */
	public Object getResult() { 
		// 에러이면 결과가 없는 것이고 
		if(this.bError) { 
			return null; 
		}
		
		if(this.bList) {
			return this.resultList; 
		}
		
		return this.resultMap; 
	}
	
	/**
	 * JSONObject를 Map형태로 변환 
	 * @param obj
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Map<String, Object> convert(JSONObject obj) { 
		if(obj == null) {
			if(this.logger.isDebugEnabled()) {
				this.logger.debug("변환할 결과가 없음");
			}
			
			return null; 
		}
		
		final Iterator<String> keyIter = obj.keys(); 
		String key = null; 
		final Map<String, Object> map = new HashMap<String, Object>(); 
		Object value = null;
		
		while(keyIter.hasNext()) {
			key = keyIter.next(); 
			value = obj.get(key); 
			
			if(value instanceof String) { 
				map.put(key, ((String) value).trim()); 
			}
			else {
				map.put(key, value); 
			}
		}
		
		return map; 
	}
	/**
	 * 결과가 empty인지 여부 
	 * @return
	 */
	public boolean isEmptyResult() { 
		return this.bEmptyResult; 
	}
	/**
	 * 기간계가 뿜어준 에러 메시지 
	 * @return
	 */
	public String getErrorMessage() { 
		return this.errorMessage; 
	}
	/**
	 * 기간계가 전달한 추가 메시지 
	 * @return
	 */
	public String getExtraMessage() { 
		return this.extraMessage; 
	}
	
	@Override 
	public String toString() {
		final StringBuilder builder = new StringBuilder(); 
		
		if(this.bError) {
			builder.append("- error : yes").append(FrameworkConstants.NEW_LINE); 
			builder.append("- message : ").append(this.errorMessage); 
		}
		else {
			builder.append("- error : no").append(FrameworkConstants.NEW_LINE); 
			builder.append("- empty : ").append(this.bEmptyResult).append(FrameworkConstants.NEW_LINE); 
			builder.append("- extra : ").append(this.extraMessage).append(FrameworkConstants.NEW_LINE); 
			builder.append("- result : ").append("type=").append((this.bList == true ? "list" : "map")).append(" --> "); 
			if(this.bList) {
				builder.append(this.resultList); 
			}
			else {
				builder.append(this.resultMap); 
			}
		}
		
		builder.append(FrameworkConstants.NEW_LINE); 
		
		return builder.toString(); 
	}
//	
//	
//	public static void main(String[] args) throws Exception {
//		JSONObject obj = null; 
//		
//		// 샘플 메시지 만들고 
//		obj = getSampleMessage(); 
//		System.out.println(obj); 
//		
//		// 분석 
//		JSONResParser parser = new JSONResParser(obj); 
//		System.out.println(parser.toString()); 
//	}
//	
//	private static JSONObject getSampleMessage() { 
//		JSONObject outObj = new JSONObject(); 
//		JSONObject resultInfoObj = new JSONObject(); 
//		JSONObject tmp = null; 
//		
//		// etcmsg 부분 
//		tmp = new JSONObject(); 
//		tmp.put("errortype", "PAM"); 
//		tmp.put("error", "0002"); 
//		tmp.put("errormsg", "계산할 내역이 없습니다."); 
//		resultInfoObj.put("etcmsg", tmp); 
//		
//		// message 부분 
//		tmp = new JSONObject(); 
//		tmp.put("description", ""); 
//		tmp.put("code", "info"); 
//		tmp.put("type", "info"); 
//		tmp.put("msg", "정상적으로 처리되었습니다."); 
//		resultInfoObj.put("message", tmp); 
//		
//		// 실제 결과 부분 
//		JSONArray resultArray = new JSONArray(); 
//		tmp = new JSONObject(); 
//		tmp.put("reservationMethod", "N"); 
//		tmp.put("dataDate", "20160304"); 
//		resultArray.add(tmp); 
//		//
//		tmp = new JSONObject(); 
//		tmp.put("reservationMethod", "N"); 
//		tmp.put("dataDate", "20160305"); 
//		resultArray.add(tmp); 
//		resultInfoObj.put("result", resultArray); 
//		
//		
//		//
//		outObj.put("resultinfo", resultInfoObj); 
//		
//		return outObj;
//	}
}
