package org.j69.eewiki.wiki.transformer;

import java.io.UnsupportedEncodingException;
import java.nio.charset.CharacterCodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;

import org.j69.eewiki.plugin.WikiPlugin;
import org.j69.eewiki.util.CacheConfig;
import org.j69.eewiki.util.CacheTagConfig;
import org.j69.eewiki.util.StringUtil;
import org.j69.eewiki.util.WebUtil;
import org.j69.eewiki.wiki.WikiFile;

import org.apache.oro.text.perl.MalformedPerl5PatternException;
import org.apache.oro.text.perl.Perl5Util;
import org.apache.oro.text.regex.PatternMatcherInput;
import org.apache.struts.util.MessageResources;

/**
 * Wiki データを通常の HTML に変換する
 *
 * @see DataLine, DataLineCreator
 */
public class WikiTransformer {
    String basePath_, str_;
    CacheTagConfig properties_;
    Map lineMap_ = new HashMap();
    String[] keywords_;
    String[] dataTags_;
    Vector wikilist_;
	HttpServletRequest request_;
	MessageResources message_;
	
    // コンフィグ
    private final static CacheConfig config_ = CacheConfig.getInstance( );

    /**
     * コンストラクタ
     *
     * @param str Wikiデータ文字列
     * @param files リンクタグに変換するためのファイル一覧
     **/
    public WikiTransformer(String basePath, String str, Vector wikilist, HttpServletRequest request, 
                                   MessageResources message) {
        basePath_ = basePath;
        str_ = str;
        wikilist_ = wikilist;
		request_ = request;
		message_ = message;
        init();
    }

    /**
     * 初期化
     *
     * @see DataLineComparator
     **/
    private void init() {
        properties_ = CacheTagConfig.getInstance();

        //キーワードの長い順でソートされるMap
        //key：キーワード　value:クラス名
        //lineMap_ = new TreeMap(new DataLineComparator());
        String keyword;
        for(Iterator i = properties_.getKeySet().iterator() ;   i.hasNext() ;) {
            keyword = (String)i.next();
            if(keyword.indexOf("tag.") == -1) {
                //行
                lineMap_.put(keyword, properties_.getConfig(keyword));
            }
        }
        keywords_ = (String[])lineMap_.keySet().toArray(new String[0]);
        Arrays.sort(keywords_, new DataLineComparator());

        dataTags_ = properties_.getConfigArray("tag");
    }

    /**
     * Wikiページ文字列に他のWikiページへのリンクタグを追加
     * @param str Wikiデータ文字列
     * @return 変換済みデータ
     */
    private String addWikiTagURLEscape(String src) {
        String tmp = src;
        Perl5Util util = new Perl5Util();

        // リンク先Wikiページが存在する場合
        // リンクタグ付加
        String pageName;
        for(Iterator it = wikilist_.iterator(); it.hasNext();) {
            WikiFile wikifile = (WikiFile)it.next();
            pageName = wikifile.getTitlename();

            if(isAutoLink(pageName)) {
                tmp = util.substitute("s!([^=^\\\"]|^)(" + pageName + ")!$1ENCODE{$2}ENCODE!g", tmp);
                tmp = util.substitute("s!([^=^\\\"]|^)\\[\\[(ENCODE{" + pageName + "}ENCODE)\\]\\]!$1$2!g", tmp);
            } else {
                tmp = util.substitute("s!([^=^\\\"]|^)\\[\\[(" + pageName + ")\\]\\]!$1ENCODE{$2}ENCODE!g", tmp);
            }

            tmp = util.substitute("s!([^=^\\\"]|^)\\[\\[" + pageName + "\\]\\]!$1ENCODE{$2}ENCODE!g", tmp);
        }

        // リンク先Wikiページが存在しない場合
        // "?"を付加、新規作成ページへのリンク
        PatternMatcherInput input = new PatternMatcherInput(tmp);
        while (util.match("m!\\[\\[(.*)\\]\\]!", input)) {
            pageName = util.group(1);
            tmp = util.substitute("s!([^=^\\\"]|^)\\[\\[(" + pageName + ")\\]\\]!$1ENCODE2{$2}ENCODE2!g", tmp);
        }
        return tmp;
    }

    /**
     * AutoLink
     */
    static boolean isAutoLink(String pageName) {
        if(!StringUtil.containsChar(pageName, "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")) return false;
        int upper = 0;
        for(int i = 0; i < pageName.length(); i++) {
            char c = pageName.charAt(i);
            if( c >= 'A' && c <= 'Z' ) {
                upper++;
            }
        }
        return ( upper > 1 );
    }

    /**
     * Wikiページ文字列に他のWikiページへのリンクタグを追加
     * @param str Wikiデータ文字列
     * @return 変換済みデータ
     */
    private String addWikiTagURL(String src) throws UnsupportedEncodingException, MalformedPerl5PatternException, CharacterCodingException {
        String tmp = src;
        Perl5Util util = new Perl5Util();

        // リンク先Wikiページが存在する場合
        // リンクタグ付加
        String pageName;

        String contextPath = request_.getContextPath();
		String viewPath = contextPath + "/Viewpage.do";
		String editPath = contextPath + "/Edit.do";

        for(Iterator it = wikilist_.iterator(); it.hasNext();) {
            WikiFile wikifile = (WikiFile)it.next();
            pageName = wikifile.getTitlename();
            tmp = util.substitute("s!(ENCODE{" + pageName + "}ENCODE)!<a href=" + viewPath + "?pid="
                    + WebUtil.getEncodeFileName(pageName) + ">" + pageName + "</a>!g", tmp);
        }

        // リンク先Wikiページが存在しない場合
        // "?"を付加
        PatternMatcherInput input = new PatternMatcherInput(tmp);
        while (util.match("m!\\ENCODE2{(.*)}ENCODE2!", input)) {
            pageName = util.group(1);
            tmp = util.substitute("s!ENCODE2{(" + pageName + ")}ENCODE2!<a href=" + editPath + "?pid="
                   + WebUtil.getEncodeFileName(pageName) + ">?</a><span class=\"noexists\">$1</span>" + "!g", tmp);
        }

        return tmp;
    }

    /**
     * 単純なタグを置き換えたデータ取得
     *
     * 処理対象キーワード
     * ''' : <em>データ</em>
     * '' : <b>データ</b>
     *
     * @return 単純なタグを置き換えたデータ
     **/
    private String getReplacedSimpleTags() {
        try {
            Perl5Util perl = new Perl5Util();
            String tmp = str_ + "\n";  // 一時的な措置、最終行の閉じタグを有効にするため

            for(int i = 0 ; i < dataTags_.length ; i++) {
                tmp = createDataTag(dataTags_[i]).get(tmp);
                //キーワードが見つかったら、対応する DataLine クラスを作成
            }
            return tmp;
        } catch(Exception e) {
            //Exception の処理は棚上げ
            //どこまで通知するか未決定
            return e.getMessage();
        }
    }

    /**
     * DataTag クラス作成.
     *
     * @param dataTag クラス名
     * @return DataTag クラス
     **/
    private DataTag createDataTag(String dataTag)
        throws InstantiationException,
                IllegalAccessException,
                ClassNotFoundException {
        return (DataTag) ((Class.forName(dataTag)).newInstance());
    }

    /**
     * WikiPlugin クラス作成.
     *
     * @param plugin クラス名
     * @return WikiPlugin クラス
     **/
    private WikiPlugin createWikiPlugin(String plugin)
        throws InstantiationException,
                IllegalAccessException,
                ClassNotFoundException {

        return (WikiPlugin) ((Class.forName(plugin)).newInstance());
    }

    /**
     * WikiデータからHTMLへのデータ変換
     *
     * @return 変換済みデータ
     **/
    public String transform() {
        try {
            final StringBuffer buf = new StringBuffer();
            str_ = addWikiTagURLEscape(str_);
            Perl5Util util = new Perl5Util();
            String cmd = "";
            String options = "";

            //単純なタグ変換をおこなう
            final String[] datas = StringUtil.split(getReplacedSimpleTags(), "\n");

            String data;
            DataLine dataLine = new BasicDataLine(), newLine;
            dataLine.init("");
            for (int i = 0; i < datas.length; i++) {
                data = datas[i];

                // #から始まる行は特殊コマンド扱いでエスケープ
                // TODO 現状タグ変換する前に実行されるのでプラグインの
                //　　　　　　なかで更にする作業が増えてしまう...どうしようかな
                if (util.match("m!^#[A-Z].+$!", data)) {
                    //キーワードが見つかったら、オプションを処理してクラス作成
                    //コマンドの取得
                    cmd = util.substitute("s!#!!g", data);
                    cmd = util.substitute("s![\\r\\n]!!g", cmd);
                    cmd = "org.j69.eewiki.plugin.Plugin" + util.substitute("s!(.+)\\(.+\\)\\?*!$1!g", cmd);

                    //オプションの取得
                    if (util.match("m!.+\\(.+\\)!", data)) {
                        options = util.substitute("s!.+\\((.+)\\)!$1!g", data);
						options = util.substitute("s![\\r\\n]!!g", options);
                    }
                    else {
                        options = "";
                    }

                    String tmpHTML = null;
                    //プラグインの起動
                    try {
                    	//プラグインクラス生成
                    	WikiPlugin wpl = createWikiPlugin(cmd);
						wpl.setRowid(i); //コマンド識別子（行番号）
						wpl.setContextPath(request_.getContextPath()); //コンテキストパス
						wpl.setPid(request_.getParameter("pid")); //ページID
						wpl.setMessageResources(message_); //メッセージリソース
						wpl.setLocale(request_.getLocale()); //ローケル
						
                        if (options != "") {
                            tmpHTML = wpl.execute(str_, options.split(","));
                        }
                        else {
                            tmpHTML = wpl.execute(str_, null);
                        }
                    } catch(Exception e) {
                        //ここの例外は無視していいんじゃないかと...思っちゃったりする
                        buf.append("Pliginエラーが発生しました");
                        buf.append(e.getMessage());
                        //return e.getMessage();
                    }

                    if (tmpHTML != null)
                        buf.append(tmpHTML);
                    continue;
                }
                newLine = getDataLine(data);
                buf.append(dataLine.closeTag(newLine));

                buf.append(newLine.openTag(dataLine));
                dataLine = newLine;

                buf.append(dataLine.get(i));
            }

            return addWikiTagURL(buf.toString());
        } catch(Exception e) {
            //Exception の処理は棚上げ
            //どこまで通知するか未決定
            return e.getMessage();
        }
    }

    /**
     * キーワードに対応した DataLine 取得
     **/
    private DataLine getDataLine(String data)
        throws InstantiationException,
                IllegalAccessException,
                ClassNotFoundException {

        String keyword;
        for(int i =0; i < keywords_.length; i++) {
            keyword = keywords_[i];
            //キーワードが見つかったら、対応する DataLine クラスを作成
            if(fastIndex(data, keyword)) {
                DataLine dataLine = createDataLine(
                                        (String)lineMap_.get(keyword));
                dataLine.init(data);

                return dataLine;
            }
        }

        //通常データ
        DataLine dataLine = new BasicDataLine();
        dataLine.init(data);

        return dataLine;
    }

    /**
     * DataLine クラス作成.
     *
     * @param dataLine クラス名
     * @return DataLine クラス
     **/
    private DataLine createDataLine(String dataLine)
        throws InstantiationException,
                IllegalAccessException,
                ClassNotFoundException {
        return (DataLine) ((Class.forName(dataLine)).newInstance());
    }

    /**
     * 文字列中の先頭にキーワードがあるかどうか
     *
     * @param str 対象文字列
     * @param keyword キーワード
     * @return true キーワードあり、false キーワードなし
     **/
    private static boolean fastIndex(String str, String keyword) {
        int index = str.indexOf(keyword);
        return ( index == 0 ? true : false );
    }
}
