/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.sql.meta;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

/**
 * テーブル情報を取得するためのユーティリティです。
 * @author nakamura
 *
 */
public final class TableInfoUtils {
	private TableInfoUtils() {
	}
	
	/**
	 * テーブル情報の一覧を取得して返します。
	 * @param dataSource データソース。
	 * @return テーブル情報の一覧。
	 */
	public static List<TableInfo> getTableInfoList(final DataSource dataSource) {
		RuntimeException e0 = null;
		try {
			final Connection con = dataSource.getConnection();
			try {
				final DatabaseMetaData metaData = con.getMetaData();
				final List<TableInfo> list = getTables(metaData);
				for (final TableInfo tableInfo : list) {
					getColumns(tableInfo, metaData);
					getPrimaryKeys(tableInfo, metaData);
					getImportedKeys(tableInfo, metaData);
					getExportedKeys(tableInfo, metaData);
				}
				return list;
			} catch (final SQLException e1) {
				e0 = new IllegalStateException(e1);
			} finally {
				con.close();
			}
		} catch (final SQLException e1) {
			if (e0 == null) {
				e0 = new IllegalStateException(e1);
			}
		}
		if (e0 != null) {
			throw e0;
		} else {
			return null;
		}
	}
	
	private static List<TableInfo> getTables(final DatabaseMetaData metaData) throws SQLException {
		final ResultSet rs = metaData.getTables(null, "%", "%", null);
		try {
			final List<TableInfo> list = new ArrayList<TableInfo>();
			while (rs.next()) {
				if ("SYSTEM TABLE".equals(rs.getString("TABLE_TYPE"))) {
					continue;
				}
				final TableInfo tableInfo = new TableInfo();
				tableInfo.setTableCatalog(rs.getString("TABLE_CAT"));
				tableInfo.setTableSchema(rs.getString("TABLE_SCHEM"));
				tableInfo.setTableName(rs.getString("TABLE_NAME"));
				list.add(tableInfo);
			}
			return list;
		} finally {
			rs.close();
		}
	}
	
	private static void getColumns(
			final TableInfo tableInfo,
			final DatabaseMetaData metaData) throws SQLException {
		final ResultSet rs = metaData.getColumns(
				tableInfo.getTableCatalog(),
				tableInfo.getTableSchema(),
				tableInfo.getTableName(), "%");
		if (tableInfo.getColNameList() == null) {
			tableInfo.setColNameList(new ArrayList<String>());
		}
		if (tableInfo.getColumnInfoMap() == null) {
			tableInfo.setColumnInfoMap(new HashMap<String, ColumnInfo>());
		}
		try {
			while (rs.next()) {
				final String colName = rs.getString("COLUMN_NAME");
				tableInfo.getColNameList().add(colName);
				final ColumnInfo colInfo = new ColumnInfo();
				colInfo.setNotNull("NO".equals(rs.getString("IS_NULLABLE")));
				colInfo.setSize(rs.getInt("COLUMN_SIZE"));
				colInfo.setDataType(rs.getInt("DATA_TYPE"));
				colInfo.setTypeName(rs.getString("TYPE_NAME"));
				colInfo.setDecimalDigits(rs.getInt("DECIMAL_DIGITS"));
				tableInfo.getColumnInfoMap().put(colName, colInfo);
			}
		} finally {
			rs.close();
		}
	}
	
	private static void getPrimaryKeys(
			final TableInfo tableInfo,
			final DatabaseMetaData metaData) throws SQLException {
		final ResultSet rs = metaData.getPrimaryKeys(
				tableInfo.getTableCatalog(),
				tableInfo.getTableSchema(),
				tableInfo.getTableName());
		if (tableInfo.getPkNameList() == null) {
			tableInfo.setPkNameList(new ArrayList<String>());
		}
		try {
			while (rs.next()) {
				tableInfo.getPkNameList().add(rs.getString("COLUMN_NAME"));
			}
		} finally {
			rs.close();
		}
	}
	
	private static boolean equals(final Object o0, final Object o1) {
		return o0 == null ? o1 == null : o0.equals(o1);
	}
	
	private static void getKeys(
			final List<ReferenceInfo> list,
			final ResultSet rs) throws SQLException {
		while (rs.next()) {
			ReferenceInfo refInfo = new ReferenceInfo();
			refInfo.setPkTableCatalog(rs.getString("PKTABLE_CAT"));
			refInfo.setPkTableSchema(rs.getString("PKTABLE_SCHEM"));
			refInfo.setPkTableName(rs.getString("PKTABLE_NAME"));
			refInfo.setPkName(rs.getString("PK_NAME"));
			refInfo.setFkTableCatalog(rs.getString("FKTABLE_CAT"));
			refInfo.setFkTableSchema(rs.getString("FKTABLE_SCHEM"));
			refInfo.setFkTableName(rs.getString("FKTABLE_NAME"));
			refInfo.setFkName(rs.getString("FK_NAME"));
			{
				final ReferenceInfo refInfo2;
				{
					final int size = list.size();
					if (size == 0) {
						refInfo2 = null;
					} else {
						refInfo2 = list.get(size - 1);
					}
				}
				if (
						refInfo2 != null
						&& equals(refInfo.getPkTableCatalog(), refInfo2.getPkTableCatalog())
						&& equals(refInfo.getPkTableSchema(), refInfo2.getPkTableSchema())
						&& equals(refInfo.getPkTableName(), refInfo2.getPkTableName())
						&& equals(refInfo.getPkName(), refInfo2.getPkName())
						&& equals(refInfo.getFkTableCatalog(), refInfo2.getFkTableCatalog())
						&& equals(refInfo.getFkTableSchema(), refInfo2.getFkTableSchema())
						&& equals(refInfo.getFkTableName(), refInfo2.getFkTableName())
						&& equals(refInfo.getFkName(), refInfo2.getFkName())) {
					refInfo = refInfo2;
				} else {
					refInfo.setPkNameList(new ArrayList<String>());
					refInfo.setFkNameList(new ArrayList<String>());
					list.add(refInfo);
				}
			}
			
			refInfo.getPkNameList().add(rs.getString("PKCOLUMN_NAME"));
			refInfo.getFkNameList().add(rs.getString("FKCOLUMN_NAME"));
		}
	}
	
	private static void getImportedKeys(
			final TableInfo tableInfo,
			final DatabaseMetaData metaData) throws SQLException {
		final ResultSet rs = metaData.getImportedKeys(
				tableInfo.getTableCatalog(),
				tableInfo.getTableSchema(),
				tableInfo.getTableName());
		
		final List<ReferenceInfo> list;
		if (tableInfo.getImportedKeysList() == null) {
			list = new ArrayList<ReferenceInfo>();
			tableInfo.setImportedKeysList(list);
		} else {
			list = tableInfo.getImportedKeysList();
		}
		
		try {
			getKeys(list, rs);
		} finally {
			rs.close();
		}
	}
	
	private static void getExportedKeys(
			final TableInfo tableInfo,
			final DatabaseMetaData metaData) throws SQLException {
		final ResultSet rs = metaData.getExportedKeys(
				tableInfo.getTableCatalog(),
				tableInfo.getTableSchema(),
				tableInfo.getTableName());
		
		final List<ReferenceInfo> list;
		if (tableInfo.getExportedKeysList() == null) {
			list = new ArrayList<ReferenceInfo>();
			tableInfo.setExportedKeysList(list);
		} else {
			list = tableInfo.getExportedKeysList();
		}
		
		try {
			getKeys(list, rs);
		} finally {
			rs.close();
		}
	}
	
	/**
	 * テーブル名をキーとした{@link Map}に変換します。
	 * @param list テーブル情報の一覧。
	 * @return テーブル名をキーとした{@link Map}。
	 */
	public static Map<String, TableInfo> convertToMap(final List<TableInfo> list) {
		final Map<String, TableInfo> map = new HashMap<String, TableInfo>(list.size());
		for (final TableInfo info : list) {
			map.put(info.getTableName(), info);
		}
		return map;
	}
}
