/*
 * Copyright 2004-2012 the Seasar Foundation and the Others.
 *
 * 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.
 */
package org.seasar.mayaa.impl.cycle.script.rhino.direct;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.mozilla.javascript.NativeObject;
import org.seasar.mayaa.PositionAware;
import org.seasar.mayaa.cycle.ServiceCycle;
import org.seasar.mayaa.cycle.scope.AttributeScope;
import org.seasar.mayaa.cycle.script.CompiledScript;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.cycle.script.rhino.NativeServiceCycle;
import org.seasar.mayaa.impl.cycle.script.rhino.WalkStandardScope;
import org.seasar.mayaa.impl.provider.ProviderUtil;

/**
 * スクリプトがGetterScriptだと解釈できるなら対応するGetterScriptを
 * 作成して返すFactory。
 * GetterScriptと呼んでいるのは、attributeまたはpropertyの取得が連なっている
 * だけのスクリプトのこと。
 * TODO 完成させる
 *
 * @author Koji Suga (Gluegent Inc.)
 */
public class GetterScriptFactory {

    /** GetterScriptとして解釈するパターン */
    protected static final Pattern GETTER_SCRIPT_PATTERN =
        Pattern.compile("\\s*([a-zA-Z_][a-zA-Z0-9_]*)" +
                "(\\s*\\.\\s*[a-zA-Z_][a-zA-Z0-9_]*|" +
                "\\s*\\[\\s*'\\s*[a-zA-Z0-9_]*\\s*'\\s*\\]|" +
                "\\s*\\[\\s*\"\\s*[a-zA-Z0-9_]*\\s*\"\\s*\\]" +
                "\\s*)*[\\s;]*");

    /** 個別getterのパターン */
    protected static final Pattern GETTER_PATTERN =
        Pattern.compile("\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)|" +
                "\\s*\\[\\s*'\\s*([a-zA-Z0-9_]*)\\s*'\\s*\\]|" +
                "\\s*\\[\\s*\"\\s*([a-zA-Z0-9_]*)\\s*\"\\s*\\]" +
                "\\s*");

    /**
     * 対応するGetterScriptを生成して返します。
     * GetterScriptでない場合はnullを返します。
     *
     * @param script 対象とするScript
     * @param position スクリプトのソースを表す情報
     * @param offsetLine スクリプトの行番号
     * @return 対応するGetterScriptまたはnull
     */
    public static CompiledScript create(
            String script, PositionAware position, int offsetLine) {
        String[] splited = splitScope(script);
        if (splited == null) {
            return null;
        }
        return createGetterScript(script, position, offsetLine, splited);
    }

    /**
     * スクリプトをスコープと変数名、プロパティ名に分割します。
     *
     * @param script
     * @return [スコープ名, 変数名, プロパティ名]のString配列。GetterScriptでない場合はnull。
     */
    protected static String[] splitScope(String script) {
        Matcher fullMatcher = GETTER_SCRIPT_PATTERN.matcher(script);
        if (fullMatcher.matches() == false) {
            return null;
        }

        List result = new ArrayList();

        String first = fullMatcher.group(1);
        if (isScopeName(first)) {
            result.add(first);
        } else {
            result.add(null);// standardScope
            result.add(first);
        }

        Matcher matcher = GETTER_PATTERN.matcher(script);
        int nextIndex = 0;
        while (matcher.find(nextIndex)) {
            result.add(getFromRange(matcher, 1, 3));
            nextIndex = matcher.end();
        }

        return (String[]) result.toArray(new String[result.size()]);
    }

    private static String getFromRange(Matcher matcher, int start, int end) {
        String result = null;
        int index = start;
        do {
            result = matcher.group(index++);
        } while (result == null && index <= end);
        return result;
    }

    /**
     * 対応するGetterScriptを生成して返します。
     * 対応していないスコープ名の場合はnullを返します。
     *
     * @param script 対象とするScript
     * @param position スクリプトのソースを表す情報
     * @param offsetLine スクリプトの行番号
     * @param scopeName 対象とするスコープ名
     * @param attributeName 変数名
     * @return 対応するGetterScriptまたはnull
     */
    protected static CompiledScript createGetterScript(
            String script, PositionAware position, int offsetLine, String[] params) {

        // TODO 無限段対応
        if (params.length > 3) {
            return null;// 無限段対応までは上限が3段階
        }

        String scopeName = params[0];
        String attributeName = null;
        String propertyName = null;
        if (params.length > 1) {
            attributeName = params[1];
        }
        if (params.length > 2) {
            propertyName = params[2];
        }

        if (containsRhinoSpecialProperty(attributeName, propertyName)) {
            return null;
        }

        if (scopeName == null || WalkStandardScope.SCOPE_NAME.equals(scopeName)) {
            return new StandardGetterScript(
                    script, position, offsetLine, scopeName, attributeName, propertyName);
        } else if (ServiceCycle.SCOPE_PAGE.equals(scopeName) || "this".equals(scopeName)) {
            return new PageGetterScript(
                    script, position, offsetLine, scopeName, attributeName, propertyName);
        } else if (ServiceCycle.SCOPE_REQUEST.equals(scopeName)) {
            return new RequestGetterScript(
                    script, position, offsetLine, scopeName, attributeName, propertyName);
        } else if (ServiceCycle.SCOPE_SESSION.equals(scopeName)) {
            return new SessionGetterScript(
                    script, position, offsetLine, scopeName, attributeName, propertyName);
        } else if (ServiceCycle.SCOPE_APPLICATION.equals(scopeName)) {
            return new ApplicationGetterScript(
                    script, position, offsetLine, scopeName, attributeName, propertyName);
        } else {
            Iterator it = ProviderUtil.getScriptEnvironment().iterateAttributeScope();
            while (it.hasNext()) {
                AttributeScope scope = (AttributeScope) it.next();
                if (scope.getScopeName().equals(scopeName)) {
                    return new AttributeScopeGetterScript(
                            script, position, offsetLine,
                            scopeName, attributeName, propertyName);
                }
            }
        }
        return null;
    }

    /**
     * 予約語かどうかを判定します。
     * ここでいう予約語とは、ServiceCycleに対する呼び出しになるものか、
     * あるいはRhinoの予約語です。
     *
     * @param word 判定する語
     * @return 予約語ならtrue
     */
    protected static boolean isReserved(String word) {
        if ("java".equals(word) || "Packages".equals(word)) {
            return true;
        }
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        NativeServiceCycle nc = new NativeServiceCycle(new NativeObject(), cycle);
        return nc.hasMember(word, nc);
    }

    /**
     * Rhinoの特殊なプロパティのうち、Javaでエミュレートしづらいものが含まれている
     * かどうかを判定します。
     * __parent__はスコープの場合に限り対応可能。__proto__は対応不能。
     *
     * @param attributeName 属性名
     * @param propertyName プロパティ名
     * @return 特殊な名前を含むならtrue
     */
    protected static boolean containsRhinoSpecialProperty(
            String attributeName, String propertyName) {
        return "__proto__".equals(attributeName) || "__proto__".equals(propertyName);
    }

    /**
     * スコープとして認識するかどうかを判定します。
     * ただしここで認識したスコープがすべて変換対象となるわけではありません。
     *
     * @param name 判定する名前
     * @return スコープとして認識する名前であればtrue
     */
    protected static boolean isScopeName(String name) {
        if (CycleUtil.getStandardScope().contains(name) || "this".equals(name)) {
            return true;
        }
        Iterator it = ProviderUtil.getScriptEnvironment().iterateAttributeScope();
        while (it.hasNext()) {
            AttributeScope scope = (AttributeScope) it.next();
            if (scope.getScopeName().equals(name)) {
                return true;
            }
        }
        return false;
    }

}
