package jp.agentec.sinaburocast.common.util;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jp.agentec.sinaburocast.common.SinaburoConstant.AttrKey;
import jp.agentec.sinaburocast.common.SinaburoConstant.MesResKey.Errors;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.seasar.framework.exception.IORuntimeException;
import org.seasar.struts.util.ActionMessagesUtil;

/**
 * HttpServletRequest, HttpServletResponse, HttpSessionを
 * 扱うメソッドはここに集約する。
 * ただし、DBアクセスは行わない。
 * staticメソッドのため、request, session等はパラメータで渡す。
 *
 * @author tsukada
 *
 */
public class ServletUtil {
	private static final Logger LOGGER = Logger.getLogger(ServletUtil.class);

	/**
	 * セッションがタイムアウトしたか?
	 *
	 * @param request
	 * @return セッションを持っていて、タイムアウトした場合のみtrue; セッションが生きている場合及びそもそもセッションがない場合はfalse
	 */
	public static boolean isSessionTimeout(HttpServletRequest request) {
		if (request == null)	return	false;

		HttpSession session = request.getSession(false);
		if (session != null)	return	false;	// セッションが存在して生きている

		return	request.getRequestedSessionId() != null;
	}

	/**
	 * ログイン中か?
	 *
	 * @param request
	 * @return
	 */
	public static boolean isLogined(HttpServletRequest request) {
		HttpSession session = request.getSession(false);
		if (session == null)	return	false;

		return	session.getAttribute(AttrKey.AUTHENTICATED_TOKEN) != null;
	}

	/**
     * @param sessionBak
     * @param atrNames
     */
    private static void backupAttribute(HttpSession session, Map<String, Object> sessionBak, String... atrNames) {
	    for (String anAtrName : atrNames) {
	    	Object anAtr = session.getAttribute(anAtrName);
	    	if (anAtr != null) {
	    		sessionBak.put(anAtrName, anAtr);
	    	}
	    }
    }

    /**
     * セッションを再作成
     * 指定された属性を引き継ぐ
     * @param request	このリクエストに紐づいたセッションを貼り替える
     * @param atrNames	新セッションへ移行したい属性名
     * @return	新しく作成されたセッション
     */
    public static HttpSession renewSession(HttpServletRequest request, String... atrNames) {
    	HttpSession session = request.getSession(false);
    	Map<String, Object>	sessionBak = new HashMap<String, Object>();
    	if (session != null) {
    		backupAttribute(session, sessionBak, atrNames);
    		backupAttribute(session, sessionBak, Globals.ERROR_KEY, Globals.MESSAGE_KEY);	// エラーとメッセージは強制的に引き継ぐ
    		try {
                session.invalidate();
            } catch (IllegalStateException e) {
            	LOGGER.info("session isn't null, but is already invalidated!");
            }
    	}
    	HttpSession newSession = request.getSession(true);
    	for (Map.Entry<String, Object> anEntry : sessionBak.entrySet()) {
    		newSession.setAttribute(anEntry.getKey(), anEntry.getValue());
    	}

    	return	newSession;
    }

	/**
	 * エラーメッセージがあるかどうかを返します.
	 *
	 * {@link ActionMessagesUtil#hasErrors(HttpServletRequest)}
	 * はエラーがセッションに入っている時falseを返すので使用しないこと!
	 *
	 * @param request リクエスト
	 * @return エラーメッセージがあるかどうか
	 */
    public static boolean hasErrors(HttpServletRequest request) {
    	if (ActionMessagesUtil.hasErrors(request))	return	true;

    	HttpSession session = request.getSession(false);
    	if (session == null)	return	false;

        ActionMessages errors = (ActionMessages)session.getAttribute(Globals.ERROR_KEY);
        return	(errors != null && !errors.isEmpty());
    }

	private static void addActionMessage(HttpServletRequest request, String key, String property, String... args) {
		ActionMessages messages = (ActionMessages)request.getAttribute(key);
		if (messages == null) {
			messages = new ActionMessages();
		}
		messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(property, args));
		request.setAttribute(key, messages);
	}

	private static void addActionMessage(HttpSession session, String key, String property, String... args) {
		ActionMessages messages = (ActionMessages)session.getAttribute(key);
		if (messages == null) {
			messages = new ActionMessages();
		}
		messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(property, args));
		session.setAttribute(key, messages);
	}

	/**
	 * メッセージ設定<br>
	 * application_ja.propertiesにpropertyとlabelが設定されていない場合に使用。
	 * @param message メッセージ
	 */
	protected static void addMessage(HttpServletRequest request, String message){
		addMessage(request, Errors.GeneralError, message);
	}

	/**
	 * エラーメッセージ<br>
	 * application_ja.propertiesにpropertyとlabelが設定されていない場合に使用。
	 * @param message エラーメッセージ
	 */
	protected static void addError(HttpServletRequest request, String message){
		addError(request, Errors.GeneralError, message);
	}

    /**
	 * エラーメッセージをリクエストに追加.
	 *
	 * application_ja.propertiesにpropertyとlabelが設定されている場合に使用。
	 * 複数メッセージを一度に追加する場合は
	 * {@link org.seasar.struts.util.ActionMessagesUtil#addErrors(HttpServletRequest, ActionMessages)} を使用のこと。
	 *
	 * @param request
	 * @param property プロパティ名. errors.YYY等
	 * @param args 置換パラメータ. プロパティ名でないことに注意。
	 */
    public static void addError(HttpServletRequest request, String property, String... args) {
    	addActionMessage(request, Globals.ERROR_KEY, property, args);
    }


    /**
	 * エラーメッセージをセッションに追加.
	 *
	 * application_ja.propertiesにpropertyとlabelが設定されている場合に使用。
	 * 複数メッセージを一度に追加する場合は
	 * {@link org.seasar.struts.util.ActionMessagesUtil#addErrors(HttpSession, ActionMessages)} を使用のこと。
	 *
	 * @param session
	 * @param property プロパティ名. errors.YYY等
	 * @param args 置換パラメータ. プロパティ名でないことに注意。
	 */
    public static void addError(HttpSession session, String property, String... args) {
    	addActionMessage(session, Globals.ERROR_KEY, property, args);
    }

    /**
	 * メッセージをリクエストに追加.
	 *
	 * application_ja.propertiesにpropertyとlabelが設定されている場合に使用。
	 * 複数メッセージを一度に追加する場合は
	 * {@link org.seasar.struts.util.ActionMessagesUtil#addMessages(HttpServletRequest, ActionMessages)} を使用のこと。
	 *
	 * @param request
	 * @param property プロパティ名. errors.YYY等
	 * @param args 置換パラメータ. プロパティ名でないことに注意。
	 */
    public static void addMessage(HttpServletRequest request, String property, String... args) {
    	addActionMessage(request, Globals.MESSAGE_KEY, property, args);
    }

    /**
	 * メッセージをセッションに追加.
	 *
	 * application_ja.propertiesにpropertyとlabelが設定されている場合に使用。
	 * 複数メッセージを一度に追加する場合は
	 * {@link org.seasar.struts.util.ActionMessagesUtil#addMessages(HttpSession, ActionMessages)} を使用のこと。
	 *
	 * @param session
	 * @param property プロパティ名. errors.YYY等
	 * @param args 置換パラメータ. プロパティ名でないことに注意。
	 */
    public static void addMessage(HttpSession session, String property, String... args) {
    	addActionMessage(session, Globals.MESSAGE_KEY, property, args);
    }


	/**
	 * コンテキスト名を返す
	 *
	 * @param request
	 * @return
	 */
	public static String getContext(HttpServletRequest request) {
		String contextPath = request.getContextPath();
		return (contextPath.length() == 0 || contextPath.equals("/"))? "": contextPath.substring(1);
	}

	public static Cookie setCookie(String key, String value, String path, int age) {
		Cookie cookie = new Cookie(key, value);
		cookie.setPath(path);
		cookie.setMaxAge(age);
		return cookie;
	}

	public static Cookie getCookie(HttpServletRequest request , String key) {
		if (request.getCookies() != null) {
			for (Cookie cookie : request.getCookies()){
				if (cookie.getName().equals(key)) {
					return cookie;
				}
			}
		}
		return null;
	}

    /**
     * BOM付きの文書をレスポンスにテキストを書き込みます。
     *
     * @param response レスポンス
     * @param text テキスト
     * @param contentType コンテントタイプ。 デフォルトはtext/plain。
     * @param encoding エンコーディング。 指定しなかった場合は、UTF-8。
     */
    public static void responseWrite(HttpServletResponse response, String text, String contentType, String encoding) {
        if (contentType == null) {
            contentType = "text/plain";
        }
        if (encoding == null) {
            encoding = "UTF-8";
        }
        try {
        	BufferedWriter out = null;
            try {
                out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), encoding));
                out.write(65279);
                out.write(text);
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    /**
     * クライアントのアドレスを返す。
     *
     * @return
     */
    public static String getRemoteAddr(HttpServletRequest request) {
    	if (PropertyUtil.getBoolean("USE_X_FORWARDED_FOR")) {
    		String addr = request.getHeader("X-Forwarded-For");
    		if (StringUtils.isNotEmpty(addr)) {
    			return addr;
    		}
    	}
   		return request.getRemoteAddr();
    }

    /**
     * UserAgentにSafariが含まれるか返す。
     *
     * @param session
     * @return
     */
    public static boolean isSafari(HttpServletRequest request) {
    	String userAgent = request.getHeader("user-agent");
    	return StringUtils.contains(userAgent, "Safari");
    }

}