package org.itscool.stylist.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.itscool.commons.bean.BeanFactory;
import org.itscool.commons.collection.InitParamMap;
import org.itscool.commons.document.XmlConfigReader;
import org.itscool.commons.logging.AbstractLog;
import org.itscool.commons.logging.SimpleLog;
import org.itscool.commons.request.CustomPageResponseWrapper;
import org.itscool.commons.util.TagInfo;
import org.itscool.commons.util.TagUtil;
import org.itscool.stylist.io.LayoutConfigReader;
import org.itscool.stylist.mapping.LayoutMapping;
import org.itscool.stylist.mapping.LayoutMappings;
import org.itscool.stylist.mapping.LayoutPutMapping;
import org.itscool.stylist.el.LayoutRegex;

/**
 * Webコンテンツのレイアウトを行うFilterクラスです<br>
 * stylistフレームワークはComposit Viewパターンを使ってJSPのレイアウトを
 * 担当するレイアウトエンジンをこのクラスで実現します。<br>
 * <hr>
 * Composite Viewパターンについて<br>
 * <hr>
 * ゼロから作業を初めてはいけません。<br>
 * 同じような家がいくつも集まる団地を建設する場合、それぞれ独立したプロジェクト
 * として各戸の作業に着手することはないでしょう。<br>
 * その代わりに、母型となる設計を基にして、ドア、窓、間取りなどの共通要素を
 * 再利用することでしょう。<br>
 * このパターンの背景にあるのはそのような考え方です。<p>
 *
 * 今日のWebアプリケーションは、メニュー、ナビゲーションバー、企業ロゴなどの、
 * 繰り返し使われる要素で構成される複雑な画面を持つのが一般的です。<p>
 *
 * Composite Viewパターンでは、画面を断片に分割し、各断片をそれぞれ完結した単位
 * としてコーディングすることを提案します。<br>
 * このパターンは、Webアプリケーションの画面のルックアンドフィールを統一するため
 * に役立ち、ビューの構成要素の再利用を促進します。<p>
 *
 * JSPを用いてComposite Viewパターンを実装するのは簡単です。
 * この実装に利用できる2つの組み込みメカニズムに <%@ include> ディレクティブと
 * includeアクションがあります。<p>
 *
 * stylistフレームワークでは、LayoutFilterを通して実行されたサーブレットの実行
 * 結果やレイアウトJSPに埋め込むJSPパスをHttpServletRequestインスタンスにセット
 * し、それらをIncludeTagのincludeディレクティブを使って呼び出しています
 * <p>
 * レイアウトの定義はstylist-config.xmlを使って以下のように定義します。
 * <p>
 * [レイアウトの設定例]
 * <p>
 * <?xml version="1.0" encoding="Shift_JIS" ?>
 * <stylist-config>
 *   <layout
 *        name="/*.do"
 *        path="/WEB-INF/jsp/layout.jsp">
 *        <!-- このレイアウトに配置するJSPを指定します                     -->
 *        <!-- サーブレットの実行結果はname=body属性にセットされます。     -->
 *        <!-- name=body属性はstylistで予約されているキーワードであるため  -->
 *        <!-- layout要素にはセットしないでください                       -->
 *        <!--【属性】                                                     -->
 *        <!-- name ： レイアウトに配置するJSPの論理名を指定します         -->
 *        <!-- path ： レイアウトに配置するJSPのパスを指定します           -->
 *        <!-- extends：-->
 *        <put name="header" path="/WEB-INF/jsp/header.jsp"/>
 *        <put name="menu" path="/WEB-INF/jsp/menu.jsp"/>
 *    </layout>
 * </layout-mappings>
 * </stylist-config>
 *
 * @author KANO 作成日： 2004/12/12
 * @version 1.11A 2005/05/20 Include機能を削除（weberとの分離）
 * @version 1.20A 2005/05/20 JSP実行結果からHTML、HEAD、BODY属性を取り除く処理を追加
 * @version 1.30A 2006/06/23 静的コンテンツ（HTML)対応追加
 */
public class LayoutFilter implements Filter {
    protected FilterConfig filterConfig = null;
    protected ServletContext context = null;
    private AbstractLog log;
    
    /**
     * LayoutFilterを初期化します<br>
     *
     * stylist-config.xmlからJSPのレイアウト情報をロードします<br>
     * init()で例外が発生するとtomcatの起動に失敗するので、例外をキャッチ
     * した場合はスローせずにcatchして処理を正常に終了させている。
     * @param config FilterConfigインスタンス
     */
    public void init(FilterConfig config) throws ServletException {
        if (filterConfig == null) {
            filterConfig = config;
        }
        if (filterConfig == null) {
            filterConfig = config;
        }
        context = filterConfig.getServletContext();
        BeanFactory factory = BeanFactory.getInstance();
        if( !factory.isInit() ){
            try{
                factory.createForUrl("di-config.xml");
            }catch(Exception ex){
                ex.printStackTrace();
                throw new ServletException(ex.getMessage());
            }
        }
        try{
            log = (AbstractLog)factory.getInstance("Log");
            if(!log.isInit()){
                log = (AbstractLog)factory.createInstance("Log");
            }
        }catch(Exception ioe){
            log = SimpleLog.getInstance();
            log.info(ioe.getMessage());
        }
        LayoutMappings includeMappings = LayoutMappings.getInstance();
        
        try {
//          ConfigReader includeReader = (ConfigReader)factory.createInstance("LayoutConfigReader");
//          includeReader.create(includeMappings);
            InitParamMap params = (InitParamMap)factory.createInstance("InitParams");
            String stylistConf = params.getParam("stylist-config");
            if(stylistConf==null){
                String msg = "no define \"stylist-config\" path from diconfig.xml[InitParams]";
                log.error(msg);
//                throw new IOException(msg);
            }else{
                LayoutConfigReader includeReader = new LayoutConfigReader();
                includeReader.setConfigName(stylistConf);
                includeReader.create(includeMappings);
            }
        } catch (Exception ioe) {
            //throw new ServletException(ioe);
            ioe.printStackTrace();
        }
    }
    
    /**
     * サーブレットリクエストの実行結果をフックして、JSPのレイアウトに埋め込みます<Br>
     * @param request ServletRequestインスタンス
     * @param response ServletResponseインスタンス
     * @param chain Filtercahinインスタンス
     */
    public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain)
        throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        
        //ServletResponseのラッパーオブジェクトを作成します
        CustomPageResponseWrapper wrapper = new CustomPageResponseWrapper(res);
        
        //元々要求されていたリソースをサーブレットに渡します。
        //ServletResponseはラッパーオブジェクトを渡し、このラッパーオブジェクトに
        //サーブレットの実行結果をセットします
        chain.doFilter(req, wrapper);
//add 2006/03/24 kano start
        //静的コンテンツ(HTML)対応
        //静的コンテンツをFrontServletでフォワード下場合、
        //静的コンテンツのサイズがHeader部にセットされ、
        //レイアウトJSP実行後もそのサイズが更新されない現象への対応
        //サーブレット実行後に、レスポンスを一旦クリアする。
        if(res.isCommitted()){
            res.reset();
        }
//add 2006/03/24 kano end
        
        try{
            //レイアウト情報を取得
            String path = req.getServletPath();
            String jsp = layout(path, req, res, chain);

            doLayout(chain, req, res, wrapper, jsp);
        }catch(Exception e){
            log.error(e);
            throw new ServletException(e.getMessage());
        }catch(Error e){
            log.error(e);
            throw new ServletException(e.getMessage());
        }
    }
    
    /**
     * 実行結果のレイアウトを行います
     * @param req HttpServletRequestインスタンス
     * @param res HttpServletResponseインスタンス
     * @param wrapper リクエストの実行結果
     * @param jsp レイアウト情報
     * @throws IOException
     * @throws ServletException
     */
    public void doLayout(
        FilterChain chain,
        HttpServletRequest req,
        HttpServletResponse res,
        CustomPageResponseWrapper wrapper,
        String jsp)
        throws IOException, ServletException {
        //リクエストURIを取得
        //String uri = (req).getRequestURI();
        //System.out.println("uri:" + uri);
        BeanFactory factory = BeanFactory.getInstance();
        InitParamMap params = (InitParamMap)factory.getInstance("InitParams");
//        InitParamMap params = InitParamMap.getInstance();
        
        String encode = params.getParam("encoding");
        if(encode == null){
            log.warn("No set inita param[encoding] default set[Shift_JIS]");
            encode = "Shift_JIS";
        }
        
        //サーブレットでフォワード/インクルードが実行された場合は
        //実行対象となるコンテンツ名を取得する
//2005.05.20 -start-
//        String contentsName =
//          (String) (req).getAttribute(Globals.REQ_PARAM_INCLUDE_CONTENTS_NAME);
//2005.05.20 -en-
        
        //サーブレットの実行結果をbyte配列で取得します
        byte[] buff = wrapper.getByteStream().toByteArray();
        
//add 2006/10/03 kano start
        //JSP1.2でEL式(簡易版)を使うための処理を追加
        String buffStr = new String(buff);
        LayoutRegex regex = new LayoutRegex(context, req);
        //./で始まるパスをWebアプリケーションからの論理パスに変換
        buffStr = regex.replacePath(buffStr);
        //EL式を展開する
        buffStr = regex.replaceEl(buffStr);
//add 2006/10/03 kano end
        
        //ContentTypeを設定
//modify 2006/11/28 kano start
        if(wrapper.getContentType() != null){
            res.setContentType(wrapper.getContentType());
//            System.out.println(wrapper.getContentType());
//            System.out.println(buffStr);
        }else{
            if(buffStr.indexOf("<?xml")>=0){
                res.setContentType("text/xml; charset=" + encode);
            }else{
                res.setContentType("text/html; charset=" + encode);
            }
        }
        log.trace("ContentType=" + wrapper.getContentType());
//modify 2006/11/28 kano end
        if(jsp==null){
            //HTMLの解析結果を出力
            PrintWriter writer = res.getWriter();
            writer.write(buffStr);
            writer.flush();
            writer.close();
            return;
        }
//2005.05.20 -start-
        //JSP実行結果をbody部とhead部に展開します
        TagInfo body = TagUtil.createTagInfo(buffStr, TagInfo.BODY_NAME);
        TagInfo head = TagUtil.createTagInfo(buffStr, TagInfo.HEAD_NAME);
        
        if( body != null ){
            //BODY内部の出力
            ((HttpServletRequest) req).setAttribute(Globals.REQ_PARAM_INCLUDE_BODY,
                body.getDataInTag());
            //BODY要素記述部の出力（レイアウトで振り分ける）
            ((HttpServletRequest) req).setAttribute(Globals.REQ_PARAM_INCLUDE_BODY_TAG,
                body.getStartTag());
            if(head != null ){
                //HEAD要素記述部の出力（レイアウトで振り分ける）
                ((HttpServletRequest) req).setAttribute(Globals.REQ_PARAM_INCLUDE_HEAD,
                    head.getDataInTag());
            }
        }else{
            ((HttpServletRequest) req).setAttribute(
                Globals.REQ_PARAM_INCLUDE_BODY,
                buffStr);
        }
//2005.05.20 -end-
        
        //レイアウト用jspをフォワードします
        try{
            filterConfig.getServletContext().getRequestDispatcher(jsp).
                forward(req, res);
        }catch(IOException e){
            String msg = jsp + "に指定されたJSPの呼出でエラーが発生しました";
            throw new IOException(msg);
        }
    }
    
    public void destroy() {
    }
    
    /**
     * レイアウト情報を取得します
     * @param path クライアントから要求されたリクエストURI
     * @param req HttpServletRequestインスタンス
     * @return インクルードを含むフォワード先のJSP名
     */
    protected String layout(String path, HttpServletRequest req,
        HttpServletResponse res, FilterChain chain)
        throws IOException, ServletException{
        LayoutMappings mappings = LayoutMappings.getInstance();
        LayoutMapping include = (LayoutMapping) mappings.get(path);
        if (include == null) {
            //System.out.println("IncludeMapping is null.");
            return null;
        }
        
        String extendz = include.getExtendz();
        if (extendz != null) {
            //親のレイアウト要素を検査します
            checkParentLayout(include, req, res, chain);
        }
        
        String layoutPath = include.getPath();
        HashMap putMap = include.getPuts();
        if (putMap != null) {
            
            //nameとpathをHttpServletRequestにセット
            //あとはJSP内のタグライブラリから<jsp:include page="${path}" />
            //を呼び出せばＯＫ！
            Set keySet = putMap.keySet();
            Iterator it = keySet.iterator();
            
            while (it.hasNext()) {
                String name = (String) it.next();
                LayoutPutMapping put = (LayoutPutMapping) putMap.get(name);
                setIncludeContents(put, name, req, res, chain);
            }
        }
        
        return layoutPath;
    }
    
    /**
     * 親のレイアウト情報を取得します
     * @param layout レイアウト情報
     * @param req HttpServletRequestインスタンス
     */
    protected void checkParentLayout(
        LayoutMapping layout,
        HttpServletRequest req, HttpServletResponse res, FilterChain chain)
        throws IOException, ServletException{
        LayoutMappings mappings = LayoutMappings.getInstance();
        String parentName = layout.getExtendz();
        String extendz = layout.getExtendz();
        //更に親の要素がセットされていないかチェック
        if (extendz != null) {
            LayoutMapping parent = (LayoutMapping) mappings.get(parentName);
            checkParentLayout(parent, req, res, chain);
        }
        
        HashMap putMap = layout.getPuts();
        if (putMap == null) {
            return;
        }
        
        //レイアウト用JSPにインクルードするJSPファイルを取得し、
        //HttpServletRequestインスタンスにセットする
        Set keySet = putMap.keySet();
        Iterator it = keySet.iterator();
        while (it.hasNext()) {
            String name = (String) it.next();
            
            LayoutPutMapping put = (LayoutPutMapping) putMap.get(name);
            setIncludeContents(put, name, req, res, chain);
            
        }
    }
    
    /**
     * レイアウト情報から部分コンテンツ情報を取得し、リクエストパラメータ
     * にセットします
     * @param put レイアウト情報
     * @param attrName レイアウト情報をあらわすユニークな名前
     * @param req HttpServletRequest情報
     * @throws IOException
     */
    protected void setIncludeContents(LayoutPutMapping put,
        String attrName, HttpServletRequest req, HttpServletResponse res,
        FilterChain chain)
        throws IOException, ServletException{
//modify 2006/10/03 kano start
//		インクルードするJSPファイルをリクエストパラメータにセットする
//		nameとpathをHttpServletRequestにセット
//		あとはJSP内のタグライブラリから<jsp:include page="${path}" />
//		を呼び出せばＯＫ！
//		req.setAttribute(Globals.REQ_PARAM_INCLUDE + attrName, put);
        
        //ServletResponseのラッパーオブジェクトを作成します
        CustomPageResponseWrapper wrapper = new CustomPageResponseWrapper(res);
        
        //元々要求されていたリソースをサーブレットに渡します。
        //ServletResponseはラッパーオブジェクトを渡し、このラッパーオブジェクトに
        //サーブレットの実行結果をセットします
        try{
            RequestDispatcher rd = req.getRequestDispatcher(put.getPath());
            rd.forward(req, wrapper);
        }catch(Exception e){
            String msg = put.getPath() + "に指定されたJSPの呼出でエラーが発生しました";
            throw new IOException(msg);
        }
        byte[] buff = wrapper.getByteStream().toByteArray();
        String buffStr = new String(buff);
        //JSPファイルからbody部を抽出します
        TagInfo body = TagUtil.createTagInfo(buffStr, TagInfo.BODY_NAME);
        if(body != null && body.getDataInTag() != null){
            buffStr = body.getDataInTag();
        }
        LayoutRegex regex = new LayoutRegex(context, req);
        //./で始まるパスをWebアプリケーションからの論理パスに変換
        buffStr = regex.replacePath(buffStr);
        //EL式を展開する
        buffStr = regex.replaceEl(buffStr);
        req.setAttribute(Globals.REQ_PARAM_INCLUDE + attrName, buffStr);
//modify 2006/10/03 kano end
    }
    
//	/**
//	 * セッションの再設定
//	 * @param req HttpServletRequestインスタンス
//	 * @param res HttpServletResponseインスタンス
//	 */
//	private void initSessionTimeout(HttpServletRequest req, HttpServletResponse res){
//		//IE6でローカルHTMLのクッキーが保持されない現象への対応
//        HttpSession session = req.getSession();
//        if( session.isNew()){
//        	String sessionId = session.getId();
//	        // クッキーの配列を取得
//	        Cookie cookies[] = req.getCookies();
//
//	        // 目的のクッキーを格納する変数
//	        Cookie accesstimeCookie = null;
//
//	        // それぞれのクッキーに対して名前を確認
//	        if(cookies != null) {
//	            for(int i = 0; i < cookies.length; i++) {
//	                // 名前が "accesstime" であるかチェック
//	                 if(cookies[i].getName().equals("JSESSIONID")) {
//	                     // 該当するクッキーを取得
//	                     accesstimeCookie = cookies[i];
//	                 }
//	             }
//	         }
//
//	        //名前が"JSESSIONID"、値が現在時刻であるクッキーを作成
//	        Cookie cookie = new Cookie("JSESSIONID",sessionId);
//
//	        // クッキーの設定
//	        cookie.setMaxAge(7 * 24 * 60 * 60); //有効期間を1週間に設定
//
//	        // クッキーを発行
//	        res.addCookie(cookie);
//	        log.debug("session is refleshed [session id="+sessionId+"]");
//        }
//	}
    
}
