/*
 *
 * 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.listeners.internal;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.dbunit.dataset.datatype.IDataTypeFactory;
import org.dbunitng.annotations.DatabaseOperationType;
import org.dbunitng.annotations.DatabaseType;
import org.dbunitng.annotations.DbUnitNG;
import org.dbunitng.exception.DbUnitNGRuntimeException;
import org.testng.ISuite;
import org.testng.ITestContext;

/**
 * データベースへの接続情報。
 * 
 * @author jyukutyo
 * 
 */
public class DbUnitNGConfig {

	/** Suiteに記述するデータソースのパラメータへのキー */
	private static final String DATASOURCE_PARAMETER = "datasource";

	/** Suiteに記述するデータソースでDBCPを指定するときの値 */
	private static final String DBCP = "dbcp";

	/** Suiteに記述するJDBCドライバクラスのパラメータへのキー */
	private static final String DRVIER = "driver";

	/** Suiteに記述するJDBC URLのパラメータへのキー */
	private static final String URL = "url";

	/** Suiteに記述するJDBCユーザー名のパラメータへのキー */
	private static final String USERNAME = "username";

	/** Suiteに記述するJDBCパスワードのパラメータへのキー */
	private static final String PASSWORD = "password";

	/** Suiteに記述するデータベーススキーマのパラメータへのキー */
	private static final String SCHEMA = "schema";

	/** Suiteに記述するデータベースへのデフォルト操作のパラメータへのキー */
	private static final String OPERATION = "defaultOperation";

	private String driver;

	private String url;

	private String userName;

	private String password;

	private boolean isDbcp;

	private String schema;

	private IDataTypeFactory factory;

	private DatabaseOperationType defaultOperation;

	public DatabaseOperationType getDefaultOperation() {
		return defaultOperation;
	}

	public void setDefaultOperation(DatabaseOperationType defaultOperation) {
		this.defaultOperation = defaultOperation;
	}

	public IDataTypeFactory getFactory() {
		return factory;
	}

	public void setFactory(IDataTypeFactory factory) {
		this.factory = factory;
	}

	public String getSchema() {
		return schema;
	}

	public void setSchema(String schema) {
		this.schema = schema;
	}

	public String getDriver() {
		return driver;
	}

	public void setDriver(String driver) {
		this.driver = driver;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public boolean isDbcp() {
		return isDbcp;
	}

	public void setDbcp(boolean isDbcp) {
		this.isDbcp = isDbcp;
	}

	/**
	 * コンテキストから接続情報を生成する。
	 * 
	 * @param context
	 *            コンテキスト
	 * @return 接続情報
	 */
	public static DbUnitNGConfig create(ITestContext context) {

		ISuite suite = context.getSuite();
		DbUnitNGConfig config =
			create(suite.getParameter(DRVIER), suite.getParameter(URL), suite
				.getParameter(PASSWORD), suite.getParameter(USERNAME), suite
				.getParameter(SCHEMA), suite.getParameter(DATASOURCE_PARAMETER));

		String operation = suite.getParameter(OPERATION);
		try {
			config.setDefaultOperation(DatabaseOperationType
				.valueOf(operation == null ? "" : operation
					.toUpperCase()));
		} catch (IllegalArgumentException e) {
			config.setDefaultOperation(DatabaseOperationType.NONE);
		}
		return config;
	}

	public static DbUnitNGConfig create(String driver, String url,
			String password, String userName, String schema, String ds) {
		DbUnitNGConfig config = new DbUnitNGConfig();
		config.setDriver(driver);

		config.setUrl(url);
		config.setFactory(getDataTypeFactory(url));

		config.setPassword(password);
		config.setUserName(userName);
		config.setSchema(schema);

		if (ds != null && DBCP.equalsIgnoreCase(ds.trim())) {
			config.setDbcp(true);
		}

		return config;

	}

	/**
	 * アノテーションから接続情報を生成する。
	 * 
	 * @param dbUnitNG
	 *            アノテーション
	 * @return 接続情報
	 */
	public static DbUnitNGConfig create(DbUnitNG dbUnitNG) {
		DbUnitNGConfig config =
			create(
				dbUnitNG.driver(),
				dbUnitNG.url(),
				dbUnitNG.password(),
				dbUnitNG.username(),
				dbUnitNG.schema(),
				dbUnitNG.datasource());
		config.setDefaultOperation(dbUnitNG.defaultOperation());
		return config;
	}

	protected static IDataTypeFactory getDataTypeFactory(String url) {

		if (url == null) {
			return null;
		}

		Pattern pattern =
			Pattern.compile("(^jdbc:)([^:]+)(:.*)", Pattern.CASE_INSENSITIVE);
		Matcher matcher = pattern.matcher(url);
		if (matcher.matches()) {
			String db = matcher.group(2);

			DatabaseType[] types = DatabaseType.values();
			for (DatabaseType type : types) {
				if (type.toString().equalsIgnoreCase(db)) {
					return type.getDataTypeFactory();
				}
			}
			return null;

		} else {
			throw new DbUnitNGRuntimeException("Jdbc url is wrong.:" + url);
		}
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(
			this,
			ToStringStyle.MULTI_LINE_STYLE);
	}

	/**
	 * 接続情報がnullでないか検証する。
	 */
	public void verifyParamsNotNull() {
		boolean isInvalid =
			this.driver == null || this.url == null || this.userName == null
				|| this.password == null;
		if (isInvalid) {
			throw new DbUnitNGRuntimeException(
				"Required Parameters are not specified in TestNG Suite."
					+ toString());
		}
	}
}
