package nlib.util;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.util.Locale;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Predicate;

import nlib.cmm.service.NlibProperty;

/**
 * <pre>
 * @Class Name : StringUtil.java
 * 
 * @Description : 문자열 관련 공통 함수 모음 
 * 
 * 
 * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
 *
 * </pre>
 *
 * @ ------------    --------    ---------------------------
 * @ 수정일                     수정자             수정내용
 * @ ------------    --------    ---------------------------
 * @ 2021. 6. 22.    KNKIM       최초 생성
 *
 * 
 * @author 이씨플라자 * DIGITALSHIP KNKIM
 * @since 2021. 6. 22.
 * @version 1.0
 *
 */
public class StringUtil extends StringUtils {
	
	private static final Logger log = LoggerFactory.getLogger(StringUtil.class);
	
	/**
	 * Null 이거나, 빈문자열(공백 포함)인 경우, true를 리턴한다.
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isEmpty(final CharSequence cs) {
		return isBlank(cs);
	}
	
	/**
	 * Null 이거나, 빈문자열(공백 포함)인 경우, replaceStr를 리턴한다.
	 * 
	 * @param str
	 * @return
	 */
	public static String getString(String str, String replaceStr) {
		if(isEmpty(str)) return replaceStr;
		else return str;
	}
	
	/**
	 * 0 이거나, 음수인 경우, replaceInt를 리턴한다.
	 * 
	 * @param str
	 * @return
	 */
	public static int getInt(int val, int replaceInt) {
		if(val <= 0) return replaceInt;
		else return val;
	}
	
	/**
	 * Null이 아니고 빈문자열(공백 포함)이 아닌 일반 문자가 존재하는 , true를 리턴한다.
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isNotEmpty(String str) {
		return !isBlank(str);
	}
	
	/**
	 * 문자열을 정수형으로 변환하여 리턴한다. 만일 문자열이 null 또는 빈 문자열인 경우, -1을 리턴한다. 
	 * 
	 * @param str
	 * @return
	 */
	public static int toNumber(String str) {
		return toNumber(str, -1);
	}
	
	/**
	 * 문자열을 정수형으로 변환하여 리턴한다. 만일 문자열이 null 또는 빈 문자열인 경우, 기본값 정보 defaultInt를 리턴한다. 
	 * 
	 * @param str
	 * @param defaultInt
	 * @return
	 */
	public static int toNumber(String str, int defaultInt) {
		if(isEmpty(str)) return defaultInt;
		
		try {
			return Integer.parseInt(str.trim());
		} catch(NumberFormatException e) { log.error("toNumber NumberFormatException : " + e.toString()); }
		
		return defaultInt;
	}
	
	/**
	 * 현재 시각을 yyyyMMddhhmmssSSS 형식으로 리턴한다. 
	 *  
	 * @return
	 */
	public static String getTimeStamp() {

		return getTimeStamp(null);
	}
	
	public static String getTimeStamp(String pattern) {

		String rtnStr = null;

		// 문자열로 변환하기 위한 패턴 설정(년도-월-일 시:분:초:초(자정이후 초))
		if(isEmpty(pattern)) pattern = "yyyyMMddhhmmssSSS";

		SimpleDateFormat sdfCurrent = new SimpleDateFormat(pattern, Locale.KOREA);
		Timestamp ts = new Timestamp(System.currentTimeMillis());

		rtnStr = sdfCurrent.format(ts.getTime());

		return rtnStr;
	}

	/**
	 * URLEncoder로 인코딩한다.
	 *  
	 * @return
	 */
	public static String encodeUrl(String str) {

		if(isEmpty(str)) return "";
		
		String encodedStr = null;
		
		try {
			encodedStr = URLEncoder.encode(str, "UTF-8");
		} catch(UnsupportedEncodingException e) {
			log.error("encodeUrl > UnsupportedEncodingException while encoding for [" + str + "] " + e.toString());
			encodedStr = str;
		}
		return encodedStr;
	}
	
	/**
	 * URLDecoder로 디코딩한다.
	 *  
	 * @return
	 */
	public static String decodeUrl(String str) {

		if(isEmpty(str)) return "";
		
		String decodedStr = null;
		
		try {
			decodedStr = URLDecoder.decode(str, "UTF-8");
		} catch(UnsupportedEncodingException e) {
			log.error("encodeUrl > UnsupportedEncodingException while encoding for [" + str + "] " + e.toString());
			decodedStr = str;
		}
		return decodedStr;
	}
	
	/**
	 * Base64로 인코딩한다.
	 *  
	 * @return
	 */
	public static String encodeBase64(String str) {

		if(isEmpty(str)) return "";
		
		Encoder encoder = Base64.getEncoder();
		return new String(encoder.encode(str.getBytes()));
	}
	
	/**
	 * Base64로 디코딩한다.
	 *  
	 * @return
	 */
	public static String decodeBase64(String str) {

		if(isEmpty(str)) return "";
		
		Decoder decoder = Base64.getDecoder();
		return new String(decoder.decode(str.getBytes()));
	}
	
	/**
	 * URL 주소에서 호스트명을 리턴한다.
	 * 
	 * 프로토콜://호스트.도메인/URI 에서 호스트명 리턴 
	 * (ex) https://seoul.nculture.org/abc/def.do  -> seoul 을 리턴한다. 
	 * 
	 * @param url
	 * @return
	 * @throws Exception
	 */
	public static String getHostName(String url) {
		if(isEmpty(url)) return "";
		
		String hostName = "";
		
		try {
			final URL urlObj = new URL(url);
			hostName = urlObj.getHost();
			if(isEmpty(hostName)) return "";
		} catch(MalformedURLException e) {
			log.error("[ERROR] getHostName(..) : MalformedURLException " + e.toString());
			return "";
		}
		if("|localhost|".contains(hostName)) hostName = NlibProperty.getString("nlib.council.dns.host", "nlib-dev");
		return (hostName.split("\\."))[0];
	}
	
	public static String addWebParamOfUrl(String url, String name, String value) {
		if(isEmpty(url)) return url;
		if(isEmpty(name)) return url;
		
		String delim = "?"; 
		if(url.contains("?")) {
			delim = "&";
		}
		
		return url + delim + name + "=" + getString(value, "");
	}
	
	public static String getSqlSearchKeyword(String str) {

		if(isNotEmpty(str)) {
			String ret = str.replaceAll("\\%", "\\\\%");
			ret = ret.replaceAll("_", "\\\\_");
			ret = ret.replaceAll("'", "\\\\'");;
			
			return ret;
	    }
		
		return str;
	}
	
	public static String getValidCodeString(String str) {

		if(isNotEmpty(str)) {
			String ret = str.replaceAll("'", "");
			ret = ret.replaceAll(" ", "");
			ret = ret.replaceAll("\t", "");;
			ret = ret.replaceAll("\n", "");;
			ret = ret.replaceAll("\r", "");;
			
			return ret;
	    }
		
		return str;
	}

	/**
	 * 게시글 제목 등 허용되지 말아야하는 일부 특수문자에 대한 치환 처리하여 리턴  
	 * 
	 * @param val
	 * @return
	 */
	public static String getRemovedQuotesStr(String val) {

		if (isEmpty(val)) return val;

		StringBuffer strBuff = new StringBuffer();
		for (int j = 0; j < val.length(); j++) {
			char c = val.charAt(j);
			switch (c) {
				case '<':
					strBuff.append("＜");
					break;
				case '>':
					strBuff.append("＞");
					break;
				case '&':
					strBuff.append("＆");
					break;
				case '"':
					strBuff.append("＂");
					break;
				case '\'':
					strBuff.append("＇");
					break;
				default:
					strBuff.append(c);
					break;
			}
		}
		
		return strBuff.toString();
	}


	/**
	 * 날짜 문자열(YYYYMMDD)을 받아서 화면 출력용 날짜 형식 문자열(YYYY-MM-DD)로 리턴한다. 
	 * 
	 * @param dateStr
	 * @param defaultStr
	 * @return
	 */
	public static String formatDateStr(String dateStr, String defaultStr) {
		
		if(isEmpty(dateStr)) return defaultStr;
		
		String tmpDateStr = dateStr.trim();
		if(4 <= tmpDateStr.length() && tmpDateStr.length() < 6) return tmpDateStr.substring(0, 4); // 연도만 있는 경우 
		else if(6 <= tmpDateStr.length() && tmpDateStr.length() < 8) return tmpDateStr.substring(0, 4) + "-" + tmpDateStr.substring(4, 6); // YYYY-MM
		else if(8 <= tmpDateStr.length()) return tmpDateStr.substring(0, 4) + "-" + tmpDateStr.substring(4, 6) + "-" + tmpDateStr.substring(6, 8); // YYYY-MM-DD
		
		return defaultStr;
	}
	
	/** 
	 * 이메일 masking 후 리턴
	 */ 
	public static String maskEmail(String email) {
		
		if (StringUtils.isEmpty(email) || !email.contains("@")) {
			return email;
		}
		
		String[] emailSplited = email.split("@");
		if (emailSplited.length != 2) {
			return email;
		}
		
		if (!StringUtils.isEmpty(emailSplited[0]) && emailSplited[0].length() > 2) {
			String str = "";
			for (int i = 2; i < emailSplited[0].length(); i++) {
				str += "*";
			}
			return email.substring(0, 2) + str + "@" + emailSplited[1];
		}
		
		return email;
	}
	
	/** * 이름 masking 후 리턴<br> * 변환 실패시 입력값 그대로 리턴<br>*/ 
	public static String maskName(String name) {
		
		if (StringUtils.isEmpty(name)) {
			return name;
		}
		
		if (name.length() == 1) {
			return "*";
		} else if (name.length() == 2) {
			return name.substring(0, 1) + "*";
		} else if (name.length() > 2) {
			String str = "";
			for (int i = 2; name.length() > i; i++)
				str += "*";
			return name.substring(0, 1) + str + name.substring(name.length() - 1, name.length());
		}
		
		return name;
	}
	
	public static String getSafeParamData(String value) {
		
		if(value == null || value.length() < 1) return value;
		
		StringBuffer strBuff = new StringBuffer();

		for (int i = 0; i < value.length(); i++) {
			char c = value.charAt(i);
			switch (c) {
			case '<':
				if ( checkNextWhiteListTag(i, value) == false )
					strBuff.append("&lt;");
				else 
					strBuff.append(c);
				//System.out.println("checkNextWhiteListTag = "+checkNextWhiteListTag(i, value));
				break;
			case '>':
				if ( checkPrevWhiteListTag(i, value) == false )
					strBuff.append("&gt;");
				else 
					strBuff.append(c);
				//System.out.println("checkPrevWhiteListTag = "+checkPrevWhiteListTag(i, value));
				break;
			case ' ':
				strBuff.append("&nbsp;");
				break;
			case '"':
				strBuff.append("&quot;");
				break;
			case '\'':
				strBuff.append("&apos;");
				break;	
			default:
				strBuff.append(c);
				break;
			}
		}
		
		value = strBuff.toString();

		// SQL INJECTION 취약점 보완 
		value = value.replaceAll("\\s+[o|O][r|R]\\s+", " o-r ");
		value = value.replaceAll("\\s+[a|A][n|N][d|D]\\s+", " a-n-d ");
		
		return value;
	}

	// Tag 화이트 리스트 ( 허용할 태그 등록 )
	static String[] whiteListTag = { "<p>","</p>","<br />" };
		
	private static boolean checkNextWhiteListTag(int index, String data) {
		String extractData = "";
		//int beginIndex = 0;
		int endIndex = 0;
		for(String whiteListData: whiteListTag) {
		    //System.out.println("===>>> whiteListData="+whiteListData);
			endIndex = index+whiteListData.length();
		    if ( data.length() > endIndex )
		    	extractData = data.substring(index, endIndex);
		    else
		    	extractData = "";
		    //System.out.println("extractData="+extractData);
		    if ( whiteListData.equals(extractData) ) return true; // whiteList 대상으로 판정
		}
		
		return false;
	}
	
	private static boolean checkPrevWhiteListTag(int index, String data) {
		String extractData = "";
		int beginIndex = 0;
		int endIndex = 0;
		for(String whiteListData: whiteListTag) {
		    //System.out.println("===>>> whiteListData="+whiteListData);
			beginIndex = index-whiteListData.length()+1;
			endIndex = index+1;
		    //System.out.println("  range ["+beginIndex+" ~ "+endIndex+"]");
		    if ( beginIndex >= 0 )
		    	extractData = data.substring(beginIndex, endIndex);
		    else
		    	extractData = "";
		    //System.out.println("extractData="+extractData);
		    if ( whiteListData.equals(extractData) ) return true; // whiteList 대상으로 판정
		}
		
		return false;
	}

	/*
	 ************************************************************************************************
	 * OWASP HTML SANITIZING 관련 : 시작  
	 ************************************************************************************************ 
	 */
	private static final Pattern ONSITE_URL = Pattern.compile("(?:[\\p{L}\\p{N}\\\\\\.\\#@\\$%\\+&;\\-_~,\\?=/!]+|\\#(\\w)+)");
	private static final Pattern OFFSITE_URL = Pattern.compile("\\s*(?:(?:ht|f)tps?://|mailto:)[\\p{L}\\p{N}]"
			+ "[\\p{L}\\p{N}\\p{Zs}\\.\\#@\\$%\\+&;:\\-_~,\\?=/!\\(\\)]*+\\s*");
	private static final Pattern EMBED_URL = Pattern.compile("^.*data:image/.*$");

	private static final Predicate<String> ONSITE_OR_OFFSITE_URL = matchesEither(ONSITE_URL, OFFSITE_URL, EMBED_URL);
	
	/**
	 * XSS 등의 공격으로 부터 보안성 유지를 위해서 HTML 허용된 태그와 속성만으로 HTML을 재구성하여 리턴한다.
	 * 
	 * @param html
	 * @return
	 */
	public static String sanitizeHtml(String html) {
		
		if(isEmpty(html)) return html;

    	PolicyFactory policy = new HtmlPolicyBuilder()
    			  .allowAttributes("src", "align", "title").onElements("img")
      			  .allowAttributes("href", "title").onElements("a")
      			  .allowAttributes("class", "height", "width", "style").globally()
      			  .allowUrlProtocols("http","https","mailto","tel","data")
      			  .allowAttributes("src").matching(ONSITE_OR_OFFSITE_URL).onElements("img")
      			  .allowElements(
      			    "a", "label", 
      			    "h1", "h2", "h3", "h4", "h5", "h6",
      			    "p", "i", "b", "u", "strong", "em", "small", "big", "pre", "code",
      			    "cite", "samp", "sub", "sup", "strike", "center", "blockquote",
      			    "hr", "br", "col", "font", "span", "div", "img",
      			    "ul", "ol", "li", "dd", "dt", "dl", "tbody", "thead", "tfoot",
      			    "table", "td", "th", "tr", "colgroup", "fieldset", "legend"
      			  )
      			  .toFactory();
    	
    	return policy.sanitize(html);
	}

	private static Predicate<String> matchesEither(final Pattern a, final Pattern b, final Pattern c) {
		return new Predicate<String>() {
			public boolean apply(String s) {
				return a.matcher(s).matches() || b.matcher(s).matches() || c.matcher(s).matches();
			}
		};
	}
	
	/*
	 ************************************************************************************************
	 * OWASP HTML SANITIZING 관련 : 종료 
	 ************************************************************************************************ 
	 */

}
