/* 
 * Copyright 2012-2016 bambooCORE, greenstep of copyright Chen Xin Nien
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * -----------------------------------------------------------------------
 * 
 * author: 	Chen Xin Nien
 * contact: [email protected]
 * 
 */
package com.netsteadfast.greenstep.sys;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.realm.ldap.JndiLdapContextFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

import com.netsteadfast.greenstep.base.AppContext;
import com.netsteadfast.greenstep.base.Constants;
import com.netsteadfast.greenstep.base.model.ScriptTypeCode;
import com.netsteadfast.greenstep.base.model.YesNo;
import com.netsteadfast.greenstep.base.sys.IUSessLogHelper;
import com.netsteadfast.greenstep.base.sys.USessLogHelperImpl;
import com.netsteadfast.greenstep.base.sys.UserAccountHttpSessionSupport;
import com.netsteadfast.greenstep.base.sys.UserCurrentCookie;
import com.netsteadfast.greenstep.util.ScriptExpressionUtils;
import com.netsteadfast.greenstep.util.SimpleUtils;
import com.netsteadfast.greenstep.vo.AccountVO;

public class GreenStepBaseFormAuthenticationFilter extends FormAuthenticationFilter {
	protected static Logger logger = Logger.getLogger(GreenStepBaseFormAuthenticationFilter.class);
	public static final String CREATE_USER_DATA_LDAP_MODE_SCRIPT = "META-INF/create-user-data-ldap-mode.groovy";	
	public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
	private static String createUserDataLdapModeScript = "";
	private String captchaParam = DEFAULT_CAPTCHA_PARAM;
	private IUSessLogHelper uSessLogHelper;
	
	public GreenStepBaseFormAuthenticationFilter() {
		super();
		uSessLogHelper = new USessLogHelperImpl();
	}
	
	public static String getCreateUserDataLdapModeScript() throws Exception {
		if ( !StringUtils.isBlank(createUserDataLdapModeScript) ) {
			return createUserDataLdapModeScript;
		}
		InputStream is = null;
		try {
			is = GreenStepBaseFormAuthenticationFilter.class.getClassLoader().getResource( CREATE_USER_DATA_LDAP_MODE_SCRIPT ).openStream();
			createUserDataLdapModeScript = IOUtils.toString(is, Constants.BASE_ENCODING);			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (is!=null) {
				is.close();
			}			
			is = null;			
		}
		return createUserDataLdapModeScript;
	}	
	
	protected String getCaptcha(ServletRequest request) {		
        return WebUtils.getCleanParam(request, this.getCaptchaParam());
    }
	
	public String getCaptchaParam() {
		return captchaParam;
	}

	public void setCaptchaParam(String captchaParam) {
		this.captchaParam = captchaParam;
	}
	
	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
		String username = StringUtils.defaultString(this.getUsername(request));
		String password = StringUtils.defaultString(this.getPassword(request));
		String captcha = StringUtils.defaultString(this.getCaptcha(request));
		//boolean rememberMe = StringUtils.defaultString(isRememberMe(request));
		boolean rememberMe = false;
		String host = StringUtils.defaultString(getHost(request));
		char pwd[] = null;
		try {
			if (this.isLoginLdapMode()) { // login by LDAP.
				pwd = password.toCharArray();
			} else { // default by DB
				ShiroLoginSupport loginSupport = new ShiroLoginSupport();
				pwd = loginSupport.getAccountService().tranPassword(password).toCharArray();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return new GreenStepBaseUsernamePasswordToken(
				username, 
				pwd, 
				rememberMe, 
				host, 
				captcha);
	}
	
	protected void doCaptchaValidate(HttpServletRequest request, GreenStepBaseUsernamePasswordToken token) {
		if (!YesNo.YES.equals(Constants.getLoginCaptchaCodeEnable())) { // 2015-12-18 add https://github.com/billchen198318/bamboobsc/issues/5
			return;
		}
		Object sessCaptcha = SecurityUtils.getSubject().getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
		String inputCaptcha = token.getCaptcha();
		if (!(sessCaptcha instanceof String) || StringUtils.isBlank(inputCaptcha) ) {
			throw new IncorrectCaptchaException("captcha error!");
		}
		if (!inputCaptcha.equals(sessCaptcha)) {
			throw new IncorrectCaptchaException("captcha error!");
		}				
	}
	
	protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
		GreenStepBaseUsernamePasswordToken token = 
				(GreenStepBaseUsernamePasswordToken) this.createToken(request, response);
		try {
			this.doCaptchaValidate((HttpServletRequest)request, token);
			
			ShiroLoginSupport loginSupport = new ShiroLoginSupport();
			AccountVO account = loginSupport.queryUserValidate(token.getUsername());
			
			Subject subject = this.getSubject(request, response);
			subject.login(token);
			
			if (this.isLoginLdapMode() && account==null) { // is no account data in DataBase, create it.
				account = this.createUserDataLdapLoginMode(token.getUsername(), new String(token.getPassword()));
			}			
			// set session
			this.setUserSession((HttpServletRequest)request, (HttpServletResponse)response, account);
			return this.onLoginSuccess(token, subject, request, response);			
		} catch (AuthenticationException e) {
			// clear session	
			logger.warn( e.getMessage().toString() );			
			UserAccountHttpSessionSupport.remove( (HttpServletRequest)request );
			this.getSubject(request, response).logout();
			return this.onLoginFailure(token, e, request, response);
		}		
	} 
	
	@Override
	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, 
			ServletRequest request, ServletResponse response) throws Exception {
		
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        if (!this.isAjaxRequest(httpServletRequest)) {
        	httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + this.getSuccessUrl());
        } else {
    		response.setCharacterEncoding( Constants.BASE_ENCODING );
    		response.setContentType("application/json");
    		response.getWriter().write(Constants.NO_AUTHZ_JSON_DATA);
        }
		return false;
	}
	
	private void setUserSession(HttpServletRequest request, HttpServletResponse response, AccountVO account) throws Exception {		
		UserAccountHttpSessionSupport.create(request, account, this.getLanguage(request));
		String httpSessionId = request.getSession().getId();
		if (StringUtils.isBlank(httpSessionId)) {
			httpSessionId = "NULL";
		}
		if ( Constants.getSystem().equals( Constants.getMainSystem() ) ) { // core-web
			List<String> currs = uSessLogHelper.findCurrenrIdByAccount(account.getAccount(), httpSessionId);
			if (currs!=null && currs.size()>0) {
				UserCurrentCookie.setCurrentId(response, currs.get(0), request.getSession().getId(), 
						account.getAccount(), this.getLanguage(request));			
				UserAccountHttpSessionSupport.createSysCurrentId(request, currs.get(0));
			}			
			SysLoginLogSupport.log( account.getAccount() );	 // only core-system need log tb_sys_login_log
		} else { // gsbsc-web, qcharts-web
			
			/*
			 * 2015-09-07 rem
			 * 
			String sysCurrentId = request.getParameter( Constants.SYS_CURRENT_ID );
			if (!StringUtils.isBlank(sysCurrentId)) {
				UserAccountHttpSessionSupport.createSysCurrentId(request, sysCurrentId);
			}
			*/
			// 2015-09-07 add
			Map<String, String> dataMap = UserCurrentCookie.getCurrentData(request);
			String sysCurrentId = dataMap.get("currentId");
			if (!StringUtils.isBlank(sysCurrentId)) {
				UserAccountHttpSessionSupport.createSysCurrentId(request, sysCurrentId);
			}
			
		}
	}
	
    protected boolean isAjaxRequest(HttpServletRequest request) {
    	if (this.isDojoxContentPane(request)) {
    		return false;
    	}
    	return "XMLHttpRequest".equalsIgnoreCase( request.getHeader("X-Requested-With") );
    }
    
    private boolean isLoginLdapMode() {
    	try {
        	if (AppContext.getBean("ldapContextFactory")!=null && (AppContext.getBean("ldapContextFactory") instanceof JndiLdapContextFactory)) {
        		return true;
        	}    		
    	} catch (NoSuchBeanDefinitionException e) {
    		// nothing...    		
    	}
    	return false;
    }
    
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
    	if ( !Constants.getSystem().equals( Constants.getMainSystem() ) && !isAjaxRequest((HttpServletRequest)request) ) { // 非 core-web
    		try {
				if ( this.loginUseCurrentCookieForGeneralPackage(request, response) ) { // no need to login-page
					String url = SimpleUtils.getHttpRequestUrl( (HttpServletRequest)request );
					logger.warn("URL = " + url );					
					WebUtils.issueRedirect(request, response, url);
					return;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
    	}    	
    	if (isAjaxRequest((HttpServletRequest)request)) {
    		response.setCharacterEncoding( Constants.BASE_ENCODING );
    		response.setContentType("application/json");
    		response.getWriter().write(Constants.NO_LOGIN_JSON_DATA);
    		return;
    	}
    	if (this.isIframeMode((HttpServletRequest)request)) { // iframe 不要導向 login.action 因為畫面會怪怪的    		
    		WebUtils.issueRedirect(request, response, "/pages/system/error_static.jsp");
    		return;
    	}    	
    	if (this.isDojoxContentPane((HttpServletRequest)request)) { // 在 dojox.layout.ContentPane 不要出現 login.action 頁面    		
    		WebUtils.issueRedirect(request, response, Constants.DOJOX_CONTENT_PANE_XHR_RE_LOGIN_PAGE);
    		return;
    	}
    	WebUtils.issueRedirect(request, response, getLoginUrl());
    }	
    
    private boolean isDojoxContentPane(HttpServletRequest request) {
    	String isDojoxContentPane = request.getParameter(Constants.IS_DOJOX_CONTENT_PANE_XHR_LOAD);
    	if (YesNo.YES.equals(isDojoxContentPane)) { // dojox.layout.ContentPane 它的 X-Requested-With 是 XMLHttpRequest
    		return true;
    	}
    	return false;
    }
    
    private boolean isIframeMode(HttpServletRequest request) {
    	String isIframeMode = request.getParameter(Constants.IS_IFRAME_MODE);
    	if (YesNo.YES.equals(isIframeMode)) {
    		return true;
    	}
    	return false;
    }
    
    /**
     * 非 core-web 登入方式 , 給 gsbsc-web, qcharts-web 用的
     * 
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    private boolean loginUseCurrentCookieForGeneralPackage(
    		ServletRequest request, ServletResponse response) throws Exception {
    	
    	Map<String, String> dataMap = UserCurrentCookie.getCurrentData( (HttpServletRequest)request );
    	if ( dataMap == null ) {
    		return false;
    	}
    	String currentId = dataMap.get("currentId");
    	String accountId = dataMap.get("account");
    	if ( StringUtils.isBlank(currentId) || StringUtils.isBlank(accountId) ) {
    		return false;
    	}
		// 先去 tb_sys_usess 用 current_id與帳戶 去查有沒有存在 db	
    	if ( this.uSessLogHelper.countByCurrent(accountId, currentId) < 1 ) { // 沒有在 core-web 登入, 所以沒有 TB_SYS_USESS 的資料
    		return false;
    	}    	
    	String captchaStr = "0123"; // 這理的 captcha 不須要比對了 , 因為不是 core-web 的登入畫面    	
    	request.setAttribute(this.captchaParam, captchaStr);
    	( (HttpServletRequest)request ).getSession().setAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY, captchaStr);
    	
    	ShiroLoginSupport loginSupport = new ShiroLoginSupport();
    	AccountVO account = loginSupport.queryUserValidate(accountId);
    	loginSupport.forceCreateLoginSubject((HttpServletRequest)request, (HttpServletResponse)response, accountId, captchaStr);
		
		// set session
		this.setUserSession((HttpServletRequest)request, (HttpServletResponse)response, account);    	    	
    	return true;
    }
    
    private String getLanguage(HttpServletRequest request) {
		String lang = request.getParameter("lang"); // core-system 取出登入頁面帶入的 lang 參數
    	if ( !Constants.getSystem().equals( Constants.getMainSystem() ) ) { // 非 core-system 所以 lang 參數在 cookie 中取出
        	Map<String, String> dataMap = UserCurrentCookie.getCurrentData( request );
        	if ( dataMap != null ) {
        		//System.out.println(dataMap);
        		lang = (String)dataMap.get("lang");
        	}
    	}		
		if ( StringUtils.isBlank(lang) ) {
			lang = "en";
		}  	
		return lang;
    }
    
    /**
     * Create need user data when login by LDAP!
     * 
     * @param account
     * @param password
     * @throws Exception
     */
    private AccountVO createUserDataLdapLoginMode(String account, String password) throws Exception {    	
    	if (account.length()>24) {
    		throw new Exception("Create user data fail! account ID length more then 24.");
    	}
    	if (password.length()>35) {
    		throw new Exception("Create user data fail! password length more then 35.");
    	}
    	ShiroLoginSupport loginSupport = new ShiroLoginSupport();
    	logger.info("create user data, login by LDAP mode, account: " + account);    	
    	Map<String, Object> paramMap = new HashMap<String, Object>();
    	paramMap.put("account", account);
    	paramMap.put("transPassword", loginSupport.getAccountService().tranPassword(password));
    	ScriptExpressionUtils.execute(ScriptTypeCode.IS_GROOVY, getCreateUserDataLdapModeScript(), null, paramMap);
    	return loginSupport.queryUser(account);
    }
	
}