package jp.sourceforge.glad.org.seasar.extension.unit;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import jp.sourceforge.glad.org.seasar.extension.dataset.impl.XlsReader;
import jp.sourceforge.glad.org.seasar.extension.unit.impl.S2UnitContextImpl;
import junit.framework.AssertionFailedError;

import org.seasar.extension.dataset.ColumnType;
import org.seasar.extension.dataset.DataReader;
import org.seasar.extension.dataset.DataRow;
import org.seasar.extension.dataset.DataSet;
import org.seasar.extension.dataset.DataTable;
import org.seasar.extension.dataset.DataWriter;
import org.seasar.extension.dataset.impl.SqlReader;
import org.seasar.extension.dataset.impl.XlsWriter;
import org.seasar.extension.dataset.types.ColumnTypes;
import org.seasar.extension.unit.BeanListReader;
import org.seasar.extension.unit.BeanReader;
import org.seasar.extension.unit.MapListReader;
import org.seasar.extension.unit.MapReader;
import org.seasar.framework.exception.ResourceNotFoundRuntimeException;
import org.seasar.framework.log.Logger;
import org.seasar.framework.util.FileOutputStreamUtil;

public abstract class S2TestCase extends org.seasar.extension.unit.S2TestCase {

    /** ロガー */
    static final Logger logger = Logger.getLogger(S2TestCase.class);

    S2UnitContext context;

    public S2TestCase() {
    }

    public S2TestCase(String name) {
        super(name);
    }

    protected S2UnitContext getContext() {
        if (context == null) {
            context = new S2UnitContextImpl();
        }
        return context;
    }

    protected void setUpDbForClass() {
        readXlsAllReplaceDb(getClassSetupXlsPath());
    }

    protected void setUpDbForClass(String... tableNames) {
        readXlsAllReplaceDb(getClassSetupXlsPath(), tableNames);
    }

    protected void setUpDbForMethod() {
        readXlsAllReplaceDb(getMethodSetupXlsPath());
    }

    protected void setUpDbForMethod(String... tableNames) {
        readXlsAllReplaceDb(getMethodSetupXlsPath(), tableNames);
    }

    protected void assertEquals(int expected, Integer actual) {
        assertEquals(expected, actual.intValue());
    }

    protected void assertEquals(long expected, Long actual) {
        assertEquals(expected, actual.longValue());
    }

    protected void assertEquals(long expected, BigInteger actual) {
        assertEquals(BigInteger.valueOf(expected), actual);
    }

    protected void assertEquals(String expected, BigInteger actual) {
        assertEquals(new BigInteger(expected), actual);
    }

    protected void assertEquals(long expected, BigDecimal actual) {
        assertEquals(BigDecimal.valueOf(expected), actual);
    }

    protected void assertEquals(String expected, BigDecimal actual) {
        assertEquals(new BigDecimal(expected), actual);
    }

    protected void assertEquals(String expected, java.util.Date actual) {
        if (actual instanceof java.sql.Date) {
            assertEquals(expected, (java.sql.Date) actual);
        } else if (actual instanceof Time) {
            assertEquals(expected, (Time) actual);
        } else if (actual instanceof Timestamp) {
            assertEquals(expected, (Timestamp) actual);
        } else {
            assertEquals(expected, new Timestamp(actual.getTime()));
        }
    }

    protected void assertEquals(String expected, java.sql.Date actual) {
        assertEquals(java.sql.Date.valueOf(expected), actual);
    }

    protected void assertEquals(String expected, Time actual) {
        assertEquals(Time.valueOf(expected), actual);
    }

    protected void assertEquals(String expected, Timestamp actual) {
        assertEquals(Timestamp.valueOf(expected), actual);
    }

    @Override
    public void assertEquals(
            String message, DataTable expected, DataTable actual) {

        message = message == null ? "" : message;
        message = message + ":TableName=" + expected.getTableName();
        assertEquals(message + ":RowSize",
                expected.getRowSize(), actual.getRowSize());
        for (int i = 0; i < expected.getRowSize(); ++i) {
            DataRow expectedRow = expected.getRow(i);
            DataRow actualRow = actual.getRow(i);
            List<String> errorMessages = new ArrayList<String>();
            for (int j = 0; j < expected.getColumnSize(); ++j) {
                try {
                    String columnName = expected.getColumnName(j);
                    Object expectedValue = expectedRow.getValue(columnName);
                    Object actualValue = actualRow.getValue(columnName);
                    if (!equalsColumnValue(expectedValue, actualValue)) {
                        assertEquals(message + ":Row=" + i
                                + ":columnName=" + columnName,
                                expectedValue, actualValue);
                    }
                } catch (AssertionFailedError e) {
                    errorMessages.add(e.getMessage());
                }
            }
            if (!errorMessages.isEmpty()) {
                fail(message + errorMessages);
            }
        }
    }

    protected boolean equalsColumnValue(Object expected, Object actual) {
        if ("*".equals(expected)) {
            return true;
        }
        ColumnType ct = ColumnTypes.getColumnType(expected);
        return ct.equals(expected, actual);
    }


    protected void assertDb() {
        assertDbForPath(getExpectedXlsPath());
    }

    protected void assertDb(String... tableNames) {
        assertDbForPath(getExpectedXlsPath(), tableNames);
    }

    protected void assertDbForSuffix(String suffix) {
        assertDbForPath(getExpectedXlsPath(suffix));
    }

    protected void assertDbForSuffix(String suffix, String... tableNames) {
        assertDbForPath(getExpectedXlsPath(suffix), tableNames);
    }

    protected void assertDbForPath(String path) {
        DataSet expected = readXls(path);
        DataSet actual = readDb(expected);
        assertEquals(expected, actual);
    }

    protected void assertDbForPath(String path, String... tableNames) {
        try {
            assertDbForPath(path);
        } catch (ResourceNotFoundRuntimeException e) {
            logger.warn("write " + path, e);
            readDbWriteXlsResource(path, tableNames);
            throw e;
        }
    }

    protected void assertData(Object actual) {
        assertDataForPath(getExpectedXlsPath(), actual);
    }

    protected void assertDataForSuffix(String suffix, Object actual) {
        assertDataForPath(getExpectedXlsPath(suffix), actual);
    }

    protected void assertDataForPath(String path, Object actual) {
        DataSet actualDS = readData(actual);
        try {
            DataSet expected = readXls(path);
            assertEquals(expected, actualDS);
        } catch (ResourceNotFoundRuntimeException e) {
            logger.warn("write " + path, e);
            writeXlsResource(path, actualDS);
            throw e;
        }
    }

    @Override
    public DataSet readXls(String path, boolean trimString) {
        DataReader reader = new XlsReader(convertPath(path), trimString);
        return reader.read();
    }

    protected void readXlsAllReplaceDb(String path, String... tableNames) {
        try {
            readXlsAllReplaceDb(path);
        } catch (ResourceNotFoundRuntimeException e) {
            logger.warn("write " + path, e);
            readDbWriteXlsResource(path, tableNames);
            throw e;
        }
    }

    protected void readDbWriteXlsResource(String path, String... tableNames) {
        writeXlsResource(path, readDbByTables(tableNames));
    }

    protected DataSet readDbByTables(String... tableNames) {
        SqlReader reader = new SqlReader(getDataSource());
        for (String tableName : tableNames) {
            reader.addTable(tableName);
        }
        return reader.read();
    }

    @SuppressWarnings("unchecked")
    protected DataSet readData(Object source) {
        if (source == null) {
            return null;
        } else if (source instanceof DataSet) {
            return (DataSet) source;
        } else if (source instanceof List) {
            List list = (List) source;
            //assertFalse(list.isEmpty());
            if (list.get(0) instanceof Map) {
                return new MapListReader(list).read();
            } else {
                return new BeanListReader(list).read();
            }
        } else if (source instanceof Object[]) {
            return readData(Arrays.asList((Object[]) source));
        } else {
            if (source instanceof Map) {
                return new MapReader((Map) source).read();
            } else {
                return new BeanReader(source).read();
            }
        }
    }

    protected void writeXlsResource(String path, DataSet dataSet) {
        File file = new File(getResourcesPath(), convertPath(path));
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        DataWriter writer = new XlsWriter(FileOutputStreamUtil.create(file));
        writer.write(dataSet);
    }

    protected String getResourcesPath() {
        return getContext().getResourcesPath();
    }

    protected String getClassSetupXlsPath() {
        return getContext().getClassSetupXlsPath(this);
    }

    protected String getMethodSetupXlsPath() {
        return getContext().getMethodSetupXlsPath(this);
    }

    protected String getExpectedXlsPath() {
        return getContext().getExpectedXlsPath(this);
    }

    protected String getExpectedXlsPath(String suffix) {
        return getContext().getExpectedXlsPath(this, suffix);
    }

}
