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 java.util.Set;

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

import com.dbs.mplus.framework.FrameworkConstants;
import com.dbs.mplus.framework.util.ConvertUtil;

import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;
import net.sf.json.util.CycleDetectionStrategy;

/**
 * JSONObject를 매번 까보는 대신에 공통적으로 필요한 부분을 꺼내기 위함 
 * 
 * 
 * @author siyoung.ahn
 *
 */
public class FatimaResParser {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	private Map<String, Object> resultMap;
	private List<Map<String, Object>> resultList; 
	private String resultStr;
	
	private JSONObject jsonObj;
	private boolean bText;
	private boolean bList;
	private boolean bEmptyResult; 
	private String topKey;
	
	/**
	 * 생성자  
	 * @param obj
	 */
	public FatimaResParser(Object obj) {
		
		if( obj instanceof JSONObject ) {
			this.bText = false;
		} 
		// 단순 String 결과를 받았을 때를 대비 -> HashMap 으로 날아옴. 바로 resultMap으로 담아줌
		else { 
			this.bText = true;
			this.bList = false;
			this.bEmptyResult = false;
		}
		
		if( !this.bText ) {
			this.jsonObj = this.getResult(obj);
			this.topKey = this.getTopKey(jsonObj);
			this.bEmptyResult = this.checkEmptyResult(jsonObj); 
			this.bList = this.checkListResultType(jsonObj);
		}
		
		// 비어있다면!
		if(this.bEmptyResult) {
			return;
		}
		
		// HashMap 인경우
		if( this.bText ) {
			this.resultMap = (Map<String, Object>) obj;
		}
		// JSONObject > map 인 경우
		else if( !this.bList ){
			JSONObject resultObj;
			if (topKey.length() > 0 ) {
				try {
					resultObj = jsonObj.getJSONObject(topKey);
				} catch (JSONException e) {
					resultObj = jsonObj;
				}
			} else {
				resultObj = jsonObj;
			}
			if(resultObj != null && !resultObj.isNullObject()) { 
				this.resultMap = this.convert(resultObj); 
			}

		} 
		// JSONObject > array<map> 인 경우
		else if( this.bList ) {
			this.resultList = new ArrayList<Map<String, Object>>(); 
			final JSONArray array = jsonObj.getJSONArray(topKey);

			// 데이터가 있는 경우에만 변환 작업  
			if(array != null && !array.isEmpty()) { 
				for(int i = 0; i < array.size(); i++) {
					this.resultList.add(this.convert(array.getJSONObject(i)));
				}				
			}
		}
	}
	
	/**
	 * 결과가 에러인지 여부 
	 * @return
	 */
	public boolean isError() { 
		return false; 
	}
	/**
	 * 결과가 리스트인지 여부 
	 * @return
	 */
	public boolean isResultList() { 
		return false; 
	}
	
	/**
	 * isResultList == true 이면 List<Map<String, String>> 반환 
	 * false이면 Map<String, String> 반환 
	 * @return
	 */
	public Object getResult() { 
		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;
		
		// value가 null일 경우 Map의 Object에 JSONObject가 들어가게됨.
		// 이것을 클라이언트에 내려주기위해서 스프링프레임워크가 jsonView로 변환할때
		// 에러가 나는듯 함
		// 따라서 클래스가 JSONObject 일 때, null으로 강제로 넣어주는데, 
		// 좀 더 테스트해서 더 확실한 방법으로 처리해야될것같음..
		
		while(keyIter.hasNext()) {
			key = keyIter.next(); 
			value = obj.get(key); 
						
			if(value instanceof JSONObject) {
				map.put(key, null);
			} else if(value instanceof String) { 
				map.put(key, ((String) value).trim());
			} else {
				map.put(key, value); 
			}
		}
		
		return map; 
	}
	/**
	 * 결과가 없는 응답인가? 
	 * @param obj
	 * @return
	 */
	private boolean checkEmptyResult(JSONObject obj) { 
		Object data;
		
		if( topKey.length() > 0 ){ 
			data = obj.get(this.topKey);
		} else {
			data = obj;
		}
		
		if(data instanceof JSONArray) { 
			if( ((JSONArray) data).size() > 0 ) {
				return false;
			} else {
				return true;
			}

		} else {
			// JSONObject 이란말인데, 이때 빈값이 어떻게 처리되는지 체크 후 구현하자
			//((JSONObject) data).size()? 
		}
		
		return false; 
	}
	
	/**
	 * 에러날 가능성있는 부분을 잡아보려고 함. 
	 */
	private JSONObject getResult(Object obj) {

		JsonConfig config = new JsonConfig();
		config.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);

		return JSONObject.fromObject(obj, config);
	}
	
	/**
	 * 파티마병원 메시지 구조
	 * 항상 최상위 키에 쌓여져 있음.
	 * 최상위 키 값 구하기
	 * 
	 * 최상위 키에 쌓여있지 않은 예외 상황을 위해서
	 * key가 여러개로 체크되는 경우 topKey를  "" 으로 리턴함
	 */
	private String getTopKey(JSONObject obj) {
		final Set<String> keySet = obj.keySet();
		final Iterator<String> keyIter = keySet.iterator();

		if(keySet.size() > 1) {
			return "";
		} else {
			return keyIter.next();
		}
	}
	
	/**
	 * 병원에서 응답 받은 결과가 list인지 여부  
	 * 
	 * 이렇게만 체크해서 최종 결과가 list인지 여부를 체크하는 것은 위험함. 
	 * 왜냐면 병원에서 결과를 list로 준다해놓고, 결과가 1건인 경우는 map으로 던지기도 하기 때문임 
	 * 결과가 list인지, map인지에 대한 것은 api 정의에 있는 것이 최종 형상임 
	 * 
	 * 이 메소드는 병원에서 응답 받은 결과가 list 형태인지 여부만 반환하는 메소드임 
	 * 
	 * @param obj
	 * @return
	 */
	private boolean checkListResultType(JSONObject obj) { 
		Object data;
		
		if( topKey.length() > 0 ){ 
			data = obj.get(this.topKey);
		} else {
			data = obj;
		}
		
		if(data instanceof JSONArray) { 
			return true; 
		}
	
		return false; 
	}
	
	/**
	 * 결과가 empty인지 여부 
	 * @return
	 */
	public boolean isEmptyResult() { 
		return bEmptyResult; 
	}
	/**
	 * 기간계가 뿜어준 에러 메시지 
	 * @return
	 */
	public String getErrorMessage() { 
		return null; 
	}
	/**
	 * 기간계가 전달한 추가 메시지 
	 * @return
	 */
	public String getExtraMessage() { 
		return null; 
	}
	
	@Override 
	public String toString() {
		final StringBuilder builder = new StringBuilder(FrameworkConstants.NEW_LINE); 
				
		builder.append(ConvertUtil.convertStringForDebug(this.resultMap)); 
		
		return builder.toString(); 
	}
}
