/*
 * FuncRegex.java
 *
 * Created on 2007/03/16, 21:54
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.itscool.stylist.el;

import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.itscool.commons.bean.BeanUtil;
import org.itscool.commons.bean.BeanUtilRuntimeException;
import org.itscool.commons.logging.AbstractLog;
import org.itscool.commons.logging.SimpleLog;

/**
 * EL式を展開する基底クラスです
 * @author KANO
 */
public class BaseElRegex {
    protected AbstractLog log = SimpleLog.getInstance();
    public static final String DEFAULT_PKG = "org.itscool.stylist.function";
    private String orgPkg = DEFAULT_PKG;
    
    /**
     * EL式に記述された関数を検索し、該当した関数を実行します<br>
     * 関数は、パッケージorg.itscool.stylist.functionに登録されている
     * デフォルト関数と引数orgPkgに指定されたパッケージの中から、
     * 該当する関数を検索します。<br>
     * たとえばHTML(srcStr)の中に以下のようなEL式が定義されている場合<br>
     *
     * <pre>
     *   実行結果は${clazz.functionA(1, 2)です。
     * </pre>
     *
     * (1) org.itscool.stylist.function.Clazz.functionA(...)を探します<br>
     * (2) (1)で関数が見つからない場合orgPkgパッケージにFunctionA.functionA
     * を探します。<br>
     * (3) 該当する関数を実行し、関数の実行結果を文字列srcStrに埋め込みます<br>
     * 関数は必ず戻り値が必要です<br>
     * @param orgPkg ルートパッケージ名
     * @param srcStr EL式が含まれている文字列
     * @return 関数の実行結果が含まれる文字列（関数が存在しない場合はsrcStrを返します）
     */
    public String replaceFunction(String orgPkg, String srcStr) {
        Pattern pattern;
        Matcher matcher;
        
        if( srcStr == null ){
            return "";
        }
        this.orgPkg = orgPkg;
        
        /* 文字列の置換(一つ一つ順々に) */
        pattern = Pattern.compile("\\$\\{\\s*[A-Za-z_0-9|(|\\)]*\\.[A-Za-z_0-9|(|\\)]*\\b\\(([A-Za-z_0-9|\\s|\\+|\\-|\"|\\'|/|:|.|,|\\(|\\)\\s]*)\\s*\\)\\s*}");
        matcher = pattern.matcher(srcStr);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String el = matcher.group();
            el = el.trim();
            String value = parseFunction(el);
            if( value == null )value = "";
            matcher.appendReplacement(sb, value);
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
    
    /**
     * ${関数定義}内に定義されている関数定義を展開します
     * @param srcStr 関数定義
     * @return 関数の実行結果を返します
     */
    public String parseFunction(String srcStr){
        Pattern pattern;
        Matcher matcher;
        
        /* 文字列の置換(一つ一つ順々に) */
        pattern = Pattern.compile("\\s*[A-Za-z_0-9|(|\\)]*\\.[A-Za-z_0-9|(|\\)]*\\b\\(([A-Za-z_0-9|\\s|\\+|\\-|\"|\\'|/|:|.|,|\\(|\\)\\s]*)\\s*\\)\\s*");
        matcher = pattern.matcher(srcStr);
        if (matcher.find()) {
            String functionStr = matcher.group();
            functionStr = functionStr.trim();
            ElementFunc func = ElementFunc.createFuncList(functionStr);
            String result = getFunctionResult(func);
//            if(result == null){
//                return null;
//            }else{
//                return result;
//            }
            return result;
        }
        return "";
    }
    
    /**
     * srcStr中の文字列から${属性名}を展開します
     * @param srcStr EL式を含む文字列
     * @return EL式を展開した結果を返します
     */
    public String replaceEl(String srcStr) {
        Pattern pattern;
        Matcher matcher;
        if( srcStr == null ){
            return "";
        }
        /* 文字列の置換(一つ一つ順々に) */
        pattern = Pattern.compile("\\$\\{\\s*\\b([A-Za-z_0-9|.|\\(|\\)]*)\\s*\\}");
        matcher = pattern.matcher(srcStr);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String el = matcher.group();
            Object value = parseObj(el);
            if( value == null ) value = "";
            matcher.appendReplacement(sb, value.toString());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
    
    /**
     * srcStr中の文字列から${属性名}を展開します
     * @param dstStr EL式を含む文字列
     * @return EL式を展開した結果を返します
     */
    public Object parseObj(String dstStr){
        StringBuffer sb2 = new StringBuffer();
        Pattern pattern2;
        Matcher matcher2;
        
        /* タグの中身を解析 */
        pattern2 = Pattern.compile("\\s*\\b([A-Za-z_0-9|.]*)\\s*");
        matcher2 = pattern2.matcher(dstStr);
        if( !matcher2.find() ){
            return null;
        }
        String tag = matcher2.group();
        
        ElementList elems = ElementList.createElementList(tag);
        Object attr = getAttribute(elems);
        return attr;
    }
    
    /**
     * 先頭が"./"で始まるパスにサーブレットのコンテキストパスに
     * 置き換えます。<br>
     * このメソッドでは、以下のタグを対象にパスの変換を行います。
     * <br>
     * <li>imgタグ
     * <li>link src、link hrefタグ
     * <li>scriptタグ
     * <li>form actionタグ
     * @param dstStr 変換もとのパス
     * @return サーブレットのコンテキストパスを付加した結果を返します
     */
    public StringBuffer parsePath(final String dstStr, final String replace) {
        Pattern pattern;
        Matcher matcher;
        StringBuffer criteria = new StringBuffer();
        criteria.append("<a.*href\\s*=\\s*\".*\"");
        criteria.append("|<img.*src\\s*=\\s*\".*\"");
        criteria.append("|<link.*src\\s*=\\s*\".*\"");
        criteria.append("|<link.*href\\s*=\\s*\".*\"");
        criteria.append("|<script.*src\\s*=\\s*\".*\"");
        criteria.append("|<.*background\\s*=\\s*\".*\"");
        criteria.append("|<form.*action\\s*=\\s*\".*\"");
        
        pattern = Pattern.compile(criteria.toString());
        matcher = pattern.matcher(dstStr);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            StringBuffer sb2 = new StringBuffer();
            String element = matcher.group();
            boolean flg = false;
            Pattern pattern2 = Pattern.compile("\"\\./");
            Matcher matcher2 = pattern2.matcher(element);
            while( matcher2.find() ){
                String path = matcher2.group();
                
                matcher2.appendReplacement(sb2, replace);
                flg = true;
            }
            matcher2.appendTail(sb2);
            String tag = sb2.toString();
            if( flg == true){
                tag = supressPath(tag);
            }
            matcher.appendReplacement(sb, tag);
        }
        matcher.appendTail(sb);
        return sb;
    }
    
    public static String supressPath(String tag){
        Pattern pattern;
        Matcher matcher;
        
        pattern = Pattern.compile("\\.\\./");
        matcher = pattern.matcher(tag);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            
            matcher.appendReplacement(sb, "");
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
    
    protected Object getAttribute( ElementList elems) {
        return elems.getTagString();
    }
    
    protected String getFunctionResult(ElementFunc func){
        String funcName = func.getFuncName();
        String clazz = func.getClassName();
        String clazzName = orgPkg + "." + clazz;
        
        try{
            clazzName = DEFAULT_PKG + "." + clazz;
            Class.forName(clazzName);
        }catch(Exception e){
            clazzName = orgPkg + "." + clazz;
        }catch(Error e){
            clazzName = orgPkg + "." + clazz;
        }
        try{
            Class.forName(clazzName);
        }catch(ClassNotFoundException e){
            throw new ElRegexException("No Function Class = "+clazzName +" Func = " + funcName);
        }catch(Error e){
            throw new ElRegexException("No Function Class = "+clazzName +" Func = " + funcName);
        }
        
        Object[] argList = convertArgument(func);
        try{
            Object result = BeanUtil.invoke(clazzName, funcName, argList);
            if( result != null ){
                return result.toString();
            }else{
                return "";
            }
        }catch(BeanUtilRuntimeException e){
            String msg = e.getMessage() + "arg num=" + argList.length
                + "args = " + toStringArgList(argList);
            log.trace(msg);
            //return null;
            throw new ElRegexException(msg);
        }
    }
    
    protected String toStringArgList(Object[] argList){
        StringBuffer sb = new StringBuffer();
        for(int i=0; i<argList.length; i++){
            String type = "null";
            String data = "null";
            
            if( argList[i] != null ){
                type = argList[i].getClass().getName();
                data = argList[i].toString();
            }
            sb.append("[").append(type).append("]");
            sb.append(data);
            if( i < argList.length){
                sb.append(",");
            }
        }
        return sb.toString();
    }
    
    protected Object[] convertArgument(ElementFunc func){
        LinkedList srcArgList = func.getArgList();
        int size = srcArgList.size();
        LinkedList dstArgList = new LinkedList();
        for(int i=0; i<size; i++){
            ElementList elementList = (ElementList)srcArgList.get(i);
            Object arg = this.getAttribute(elementList);
            if(arg==null){
                //dstArgList.add(elementList.getTagString());
                dstArgList.add(null);
            }else{
                if( arg instanceof String){
                    String argStr = arg.toString();
                    int strToken1 = argStr.trim().indexOf("'");
                    int strToken2 = argStr.trim().lastIndexOf("'");
                    if( strToken1 == 0 && strToken2 == argStr.length()-1 ){
                        arg = BeanUtil.supressQuate(argStr);
                    }
                }
                dstArgList.add(arg);
            }
        }
        return dstArgList.toArray();
    }
}
