/*
 *
 * 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.dbunitng.assertion;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import junit.framework.Assert;

import org.dbunit.Assertion;
import org.dbunit.DatabaseUnitException;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ITableMetaData;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunitng.annotations.FileType;
import org.dbunitng.dataset.BeanListConverter;
import org.dbunitng.exception.DbUnitNGRuntimeException;
import org.dbunitng.listeners.DbUnitNGContext;
import org.dbunitng.util.ResourceUtil;

/**
 * アサーションヘルパークラス。
 * 
 * @author jyukutyo
 * 
 */
public class AssertionHelper {

	/**
	 * hide constructor.
	 */
	protected AssertionHelper() {
	// empty
	}

	/**
	 * <p>
	 * ファイルに記述したカラムのみ比較する。
	 * </p>
	 * 期待値を記述したファイルは、引数のクラスがあるパッケージの以下のサブディレクトリに格納したファイルとなる。
	 * 
	 * <pre>
	 * assertEqualsOnlyColumnsInFile(beanList, org.dbunitng.test.Sample.class, &quot;sub1/sub2&quot;, &quot;test.xml&quot;) =&gt; org/dbunitng/test/sub1/sub2/test.xml
	 * </pre>
	 * 
	 * @param beanList
	 *            Beanのリスト
	 * @param subDirectoryPath
	 *            サブディレクトリのパス
	 * @param fileName
	 *            ファイル名
	 * @throws DatabaseUnitException
	 */
	public static void assertEqualsOnlyColumnsInFile(List<?> beanList,
			String subDirectoryPath, String fileName) {
		BeanListConverter converter = new BeanListConverter(beanList);
		IDataSet actual = converter.convert();
		assertEqualsOnlyColumnsInFile(actual, subDirectoryPath, fileName);
	}

	/**
	 * <p>
	 * ファイルに記述したカラムのみ比較する。
	 * </p>
	 * 期待値を記述したファイルは、引数のクラスがあるパッケージの以下のサブディレクトリに格納したファイルとなる。
	 * 
	 * <pre>
	 * assertEqualsOnlyColumnsInFile(actual, org.dbunitng.test.Sample.class, &quot;sub1/sub2&quot;, &quot;test.xml&quot;) =&gt; org/dbunitng/test/sub1/sub2/test.xml
	 * </pre>
	 * 
	 * @param actual
	 *            実際の結果
	 * @param subDirectoryPath
	 *            サブディレクトリのパス
	 * @param fileName
	 *            ファイル名
	 * @throws DatabaseUnitException
	 */
	public static void assertEqualsOnlyColumnsInFile(IDataSet actual,
			String subDirectoryPath, String fileName) {
		
		DbUnitNGContext context = DbUnitNGContext.currentContext();
		Class<?> clazz = context.getTestClass();
		
		String pathName =
			ResourceUtil
				.replacePackageToDirectory(clazz.getPackage().getName())
				+ addDelimiter(subDirectoryPath) + addDelimiter(fileName);

		assertEqualsOnlyColumnsInFileWithPath(actual, pathName);

	}

	/**
	 * デリミタがなければデリミタを付与して返す。
	 * 
	 * @param str
	 * @return デリミタを付与した文字列
	 */
	protected static String addDelimiter(String str) {
		return str.indexOf('/') == 0 ? str : "/" + str;
	}

	/**
	 * <p>
	 * ファイルに記述したカラムのみ比較する。
	 * </p>
	 * 期待値を記述したファイルは、引数のクラスがあるパッケージに格納したファイルとなる。
	 * 
	 * <pre>
	 * assertEqualsOnlyColumnsInFile(actual, org.dbunitng.test.Sample.class, &quot;test.xml&quot;) =&gt; org/dbunitng/test/test.xml
	 * </pre>
	 * 
	 * @param actual
	 *            実際の結果
	 * @param fileName
	 *            ファイル名
	 * @throws DatabaseUnitException
	 */
	public static void assertEqualsOnlyColumnsInFile(IDataSet actual,
			String fileName) {

		DbUnitNGContext context = DbUnitNGContext.currentContext();
		Class<?> clazz = context.getTestClass();
		
		String pathName =
			ResourceUtil
				.replacePackageToDirectory(clazz.getPackage().getName())
				+ addDelimiter(fileName);

		assertEqualsOnlyColumnsInFileWithPath(actual, pathName);
	}

	/**
	 * <p>
	 * ファイルに記述したカラムのみ比較する。
	 * </p>
	 * 期待値を記述したファイルは、引数のクラスがあるパッケージに格納したファイルとなる。
	 * 
	 * <pre>
	 * assertEqualsOnlyColumnsInFile(beanList, org.dbunitng.test.Sample.class, &quot;test.xml&quot;) =&gt; org/dbunitng/test/test.xml
	 * </pre>
	 * 
	 * @param beanList
	 *            Beanのリスト
	 * @param fileName
	 *            ファイル名
	 * @throws DatabaseUnitException
	 */
	public static void assertEqualsOnlyColumnsInFile(List<?> beanList,
			String fileName) {
		BeanListConverter converter = new BeanListConverter(beanList);
		IDataSet actual = converter.convert();
		assertEqualsOnlyColumnsInFile(actual, fileName);
	}

	/**
	 * <p>
	 * ファイルに記述したカラムのみ比較する。
	 * </p>
	 * 
	 * @param beanList
	 *            Beanのリスト
	 * @param pathName
	 *            ファイルのパス
	 * @throws DatabaseUnitException
	 */
	public static void assertEqualsOnlyColumnsInFileWithPath(List<?> beanList,
			String pathName) {
		BeanListConverter converter = new BeanListConverter(beanList);
		IDataSet actual = converter.convert();
		assertEqualsOnlyColumnsInFileWithPath(actual, pathName);
	}

	/**
	 * <p>
	 * ファイルに記述したカラムのみ比較する。
	 * </p>
	 * 
	 * @param actual
	 *            実際の結果
	 * @param pathName
	 *            ファイルのパス
	 * @throws DatabaseUnitException
	 */
	public static void assertEqualsOnlyColumnsInFileWithPath(IDataSet actual,
			String pathName) {

		InputStream stream = ResourceUtil.getResourceAsStream(pathName);

		FileType type =
			ResourceUtil.toFileType(ResourceUtil.getExtension(pathName));

		try {
			IDataSet dataSet;
			if (FileType.EXCEL == type) {
				dataSet = new XlsDataSet(stream);
			} else {
				dataSet = new FlatXmlDataSet(stream);
			}
			ReplacementDataSet expected = new ReplacementDataSet(dataSet);
			expected.addReplacementObject("[null]", null);
			assertEquals(expected, actual);
		} catch (IOException e) {
			throw new DbUnitNGRuntimeException(e);
		} catch (DatabaseUnitException e) {
			throw new DbUnitNGRuntimeException(e);
		}
	}

	/**
	 * Asserts that the two specified dataset are equals. This method ignore the
	 * tables order.
	 */
	public static void assertEquals(IDataSet expectedDataSet,
			IDataSet actualDataSet) throws DatabaseUnitException {

		// do not continue if same instance
		if (expectedDataSet == actualDataSet) {
			return;
		}

		String[] expectedNames = getSortedUpperTableNames(expectedDataSet);
		String[] actualNames = getSortedUpperTableNames(actualDataSet);

		// tables count
		Assert.assertEquals(
			"table count",
			expectedNames.length,
			actualNames.length);

		// table names in no specific order
		for (int i = 0; i < expectedNames.length; i++) {
			if (!actualNames[i].equals(expectedNames[i])) {
				Assert.fail("expected tables " + Arrays.asList(expectedNames)
					+ " but was " + Arrays.asList(actualNames));
			}

		}

		// tables
		for (int i = 0; i < expectedNames.length; i++) {
			String name = expectedNames[i];

			// change to assert only columns in expected table
			ITable expectedTable = expectedDataSet.getTable(name);
			ITable actualTable =
				DefaultColumnFilter.includedColumnsTable(actualDataSet
					.getTable(name), expectedTable
					.getTableMetaData()
					.getColumns());
			Assertion.assertEquals(expectedTable, actualTable);
		}
	}

	////////////////////////////////////////////////////////////////////////////
	// below methods and class are copies of DbUnit's Assertion class
	// because of their private modifier.

	protected static final ColumnComparator COLUMN_COMPARATOR =
		new ColumnComparator();

	protected static Column[] getSortedColumns(ITableMetaData metaData)
			throws DataSetException {

		Column[] columns = metaData.getColumns();
		Column[] sortColumns = new Column[columns.length];
		System.arraycopy(columns, 0, sortColumns, 0, columns.length);
		Arrays.sort(sortColumns, COLUMN_COMPARATOR);
		return sortColumns;
	}

	protected static class ColumnComparator implements Comparator<Column> {

		public int compare(Column column1, Column column2) {

			String columnName1 = column1.getColumnName();
			String columnName2 = column2.getColumnName();
			return columnName1.compareToIgnoreCase(columnName2);
		}
	}

	protected static String[] getSortedUpperTableNames(IDataSet dataSet)
			throws DataSetException {

		String[] names = dataSet.getTableNames();
		for (int i = 0; i < names.length; i++) {
			names[i] = names[i].toUpperCase();
		}
		Arrays.sort(names);
		return names;
	}
}
