/*
 * Copyright 2004-2015 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.framework.unit.impl;

import java.lang.reflect.Method;
import java.sql.Types;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.seasar.extension.dataset.ColumnType;
import org.seasar.extension.dataset.DataSet;
import org.seasar.extension.dataset.types.ColumnTypes;
import org.seasar.framework.aop.interceptors.MockInterceptor;
import org.seasar.framework.container.AspectDef;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.container.annotation.tiger.DestroyMethod;
import org.seasar.framework.container.annotation.tiger.InitMethod;
import org.seasar.framework.container.deployer.InstanceDefFactory;
import org.seasar.framework.container.factory.S2ContainerFactory;
import org.seasar.framework.container.factory.TigerAnnotationHandler;
import org.seasar.framework.convention.NamingConvention;
import org.seasar.framework.message.MessageResourceBundleFactory;
import org.seasar.framework.unit.ConfigFileIncluder;
import org.seasar.framework.unit.ExpectedDataReader;
import org.seasar.framework.unit.InternalTestContext;
import org.seasar.framework.unit.PreparationType;
import org.seasar.framework.unit.TestDataPreparer;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.ResourceUtil;
import org.seasar.framework.util.tiger.CollectionsUtil;

/**
 * Servlet、JTA、EJB3のAPIに依存せずにS2JUnit4を実行可能にするシンプルなテストコンテキストです。
 * 
 * @author taedium
 */
public class SimpleInternalTestContext implements InternalTestContext {

    /** Tigerのアノテーションハンドラー */
    protected final TigerAnnotationHandler handler = new TigerAnnotationHandler();

    /** モックインターセプターのリスト */
    protected final List<MockInterceptor> mockInterceptors = CollectionsUtil
            .newArrayList();

    /** ルートのS2コンテナ */
    protected S2Container container;

    /** 命名規約 */
    protected NamingConvention namingConvention;

    /** テストクラス */
    protected Class<?> testClass;

    /** テストメソッド */
    protected Method testMethod;

    /** 自動インクルードをするかどうかを表すフラグ。デフォルトは<code>true</code> */
    protected boolean autoIncluding = true;

    /** テストデータを自動準備するかどうかを表すフラグ。デフォルトは<code>true</code> */
    protected boolean autoPreparing = true;

    /** テストデータの準備方法 */
    protected PreparationType preparationType = PreparationType.WRITE;

    /** テストデータの文字列に含まれる空白を取り除くかどうかを表すフラグ。デフォルトは<code>true</code> */
    protected boolean trimString = true;

    /** EJB3を使用するかどうかを表すフラグ。デフォルトは<code>false</code> */
    protected boolean ejb3Enabled = false;

    /** JTAを使用するかどうかを表すフラグ。デフォルトは<code>false</code> */
    protected boolean jtaEnabled = false;

    /** S2コンテナが初期化されたかどうかを表すフラグ */
    protected boolean containerInitialized;

    /** {@link ColumnTypes}から登録除去された{@link ColumnType カラムの型}をクラスをキーにして管理するマップ */
    protected Map<Class<?>, ColumnType> deregisteredColumnTypesByClass = new HashMap<Class<?>, ColumnType>();

    /** {@link ColumnTypes}から登録除去された{@link ColumnType カラムの型}をSQL型をキーにして管理するマップ */
    protected Map<Integer, ColumnType> deregisteredColumnTypesBySqlType = new HashMap<Integer, ColumnType>();

    /**
     * S2コンテナを設定します。
     * 
     * @param container
     *            S2コンテナ
     */
    @Binding(bindingType = BindingType.MUST)
    public void setContainer(final S2Container container) {
        this.container = container.getRoot();
    }

    /**
     * EJB3を使用する場合<code>true</code>を設定します。
     * 
     * @param ejb3Enabled
     *            EJB3を使用する場合<code>true</code>、使用しない場合<code>false</code>
     */
    @Binding(bindingType = BindingType.MAY)
    public void setEjb3Enabled(boolean ejb3Enabled) {
        this.ejb3Enabled = ejb3Enabled;
    }

    /**
     * JTAを使用する場合<code>true</code>を設定します。
     * 
     * @param jtaEnabled
     *            JTAを使用する場合<code>true</code>、使用しない場合<code>false</code>
     */
    @Binding(bindingType = BindingType.MAY)
    public void setJtaEnabled(boolean jtaEnabled) {
        this.jtaEnabled = jtaEnabled;
    }

    public void setAutoIncluding(final boolean autoIncluding) {
        this.autoIncluding = autoIncluding;
    }

    public void setAutoPreparing(final boolean autoPreparing) {
        this.autoPreparing = autoPreparing;
    }

    public PreparationType getPreparationType() {
        return preparationType;
    }

    public void setPreparationType(PreparationType preparationType) {
        this.preparationType = preparationType;
    }

    public void setTrimString(final boolean trimString) {
        this.trimString = trimString;
    }

    public boolean isTrimString() {
        return trimString;
    }

    public void setTestClass(final Class<?> testClass) {
        this.testClass = testClass;
    }

    public void setTestMethod(final Method testMethod) {
        this.testMethod = testMethod;
    }

    @Binding(bindingType = BindingType.NONE)
    public void setNamingConvention(NamingConvention namingConvention) {
        this.namingConvention = namingConvention;
    }

    /**
     * このコンポーネントを初期化します。
     * 
     * @throws Throwable
     */
    @InitMethod
    public void init() throws Throwable {
    }

    /**
     * このコンポーネントを破棄します。
     */
    @DestroyMethod
    public void destroy() {
        MessageResourceBundleFactory.clear();
    }

    public void initContainer() {
        container.init();
        containerInitialized = true;
    }

    public void destroyContainer() {
        container.destroy();
        container = null;
        containerInitialized = false;
    }

    public void include() {
        if (autoIncluding) {
            if (container.hasComponentDef(ConfigFileIncluder.class)) {
                final ConfigFileIncluder includer = (ConfigFileIncluder) container
                        .getComponent(ConfigFileIncluder.class);
                includer.include(this);
            }
        }
    }

    public void include(final String path) {
        final String convertedPath = ResourceUtil.convertPath(path, testClass);
        S2ContainerFactory.include(container, convertedPath);
    }

    public void register(final Class<?> componentClass,
            final String componentName) {
        final ComponentDef cd = handler.createComponentDef(componentClass,
                InstanceDefFactory.SINGLETON);
        cd.setComponentName(componentName);
        handler.appendDI(cd);
        handler.appendAspect(cd);
        handler.appendInterType(cd);
        handler.appendInitMethod(cd);
        handler.appendDestroyMethod(cd);
        container.register(cd);
    }

    public void register(final Class<?> componentClass) {
        if (namingConvention == null) {
            register(componentClass, null);
        } else {
            register(componentClass, namingConvention
                    .fromClassNameToComponentName(componentClass.getName()));
        }
    }

    public void register(final ComponentDef componentDef) {
        container.register(componentDef);
    }

    public void register(final Object component, String componentName) {
        container.register(component, componentName);
    }

    public void register(final Object component) {
        container.register(component);
    }

    public String getTestClassPackagePath() {
        return testClass.getName().replace('.', '/')
                .replaceFirst("/[^/]+$", "");
    }

    public String getTestClassShortName() {
        return ClassUtil.getShortClassName(testClass);
    }

    public String getTestMethodName() {
        return testMethod.getName();
    }

    public void prepareTestData() {
        if (autoPreparing) {
            if (hasComponentDef(TestDataPreparer.class)) {
                final TestDataPreparer preparer = getComponent(TestDataPreparer.class);
                preparer.prepare(this);
            }
        }
    }

    public DataSet getExpected() {
        if (hasComponentDef(ExpectedDataReader.class)) {
            final ExpectedDataReader reader = getComponent(ExpectedDataReader.class);
            final DataSet expected = reader.read(this);
            if (expected != null) {
                return expected;
            }
        }
        return null;
    }

    public MockInterceptor getMockInterceptor(final int index) {
        return mockInterceptors.get(index);
    }

    public S2Container getContainer() {
        return container;
    }

    public <T> T getComponent(final Class<? extends T> componentKey) {
        return componentKey.cast(container.getComponent(componentKey));
    }

    public Object getComponent(final Object componentKey) {
        return container.getComponent(componentKey);
    }

    public boolean hasComponentDef(final Object componentKey) {
        return container.hasComponentDef(componentKey);
    }

    public ComponentDef getComponentDef(final int index) {
        return container.getComponentDef(index);
    }

    public ComponentDef getComponentDef(final Object componentKey) {
        return container.getComponentDef(componentKey);
    }

    public void addMockInterceptor(final MockInterceptor mockInterceptor) {
        mockInterceptors.add(mockInterceptor);
    }

    public int getMockInterceptorSize() {
        return mockInterceptors.size();
    }

    public void addAspecDef(final Object componentKey, final AspectDef aspectDef) {
        if (containerInitialized) {
            throw new IllegalStateException();
        }
        container.getComponentDef(componentKey).addAspectDef(0, aspectDef);
    }

    public boolean isEjb3Enabled() {
        return ejb3Enabled;
    }

    public boolean isJtaEnabled() {
        return jtaEnabled;
    }

    public void registerColumnTypes() {
        if (!trimString) {
            registerColumnType(String.class, ColumnTypes.NOT_TRIM_STRING);
            registerColumnType(Types.CHAR, ColumnTypes.NOT_TRIM_STRING);
            registerColumnType(Types.LONGVARCHAR, ColumnTypes.NOT_TRIM_STRING);
            registerColumnType(Types.VARCHAR, ColumnTypes.NOT_TRIM_STRING);
        }
    }

    /**
     * カラムの型を登録します。
     * 
     * @param clazz
     *            クラス
     * @param columnType
     *            カラムの型
     */
    protected void registerColumnType(Class<?> clazz, ColumnType columnType) {
        ColumnType original = ColumnTypes.registerColumnType(clazz, columnType);
        deregisteredColumnTypesByClass.put(clazz, original);
    }

    /**
     * カラムの型を登録します。
     * 
     * @param sqlType
     *            SQL型
     * @param columnType
     *            カラムの型
     */
    protected void registerColumnType(int sqlType, ColumnType columnType) {
        ColumnType original = ColumnTypes.registerColumnType(sqlType,
                columnType);
        deregisteredColumnTypesBySqlType.put(sqlType, original);
    }

    public void revertColumnTypes() {
        for (Map.Entry<Class<?>, ColumnType> e : deregisteredColumnTypesByClass
                .entrySet()) {
            ColumnTypes.registerColumnType(e.getKey(), e.getValue());
        }
        for (Map.Entry<Integer, ColumnType> e : deregisteredColumnTypesBySqlType
                .entrySet()) {
            ColumnTypes.registerColumnType(e.getKey(), e.getValue());
        }
    }
}
