/*
 * 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.samples.gcrud;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import woolpack.fn.Fn;
import woolpack.fn.FnUtils;
import woolpack.sql.meta.ReferenceInfo;
import woolpack.sql.meta.TableInfo;

/**
 * DB のメタ情報を利用して DB へのアクセス結果をメタ情報で装飾する汎用の DB アクセサです。
 * DB アクセサメソッドの引数の内"tableName"をテーブル特定のために使用します。
 * @author nakamura
 *
 */
public class GcrudManager2 {
	
	private static final Fn<Map<String, List<Object>>, Map<String, Object>, RuntimeException> MAP_CONVERTER =
		new Fn<Map<String, List<Object>>, Map<String, Object>, RuntimeException>() {
		public Map<String, Object> exec(final Map<String, List<Object>> c) {
			final Map<String, Object> map = new HashMap<String, Object>(c.size());
			for (final Entry<String, List<Object>> entry : c.entrySet()) {
				if (entry.getValue() != null && entry.getValue().size() == 1) {
					final Object value = entry.getValue().get(0);
					if (value != null && value.toString().length() > 0) {
						map.put(entry.getKey(), value);
					}
				} else {
					map.put(entry.getKey(), entry.getValue());
				}
			}
			return map;
		}
	};
	
	private GcrudManager manager;
	private Fn<Map<String, List<Object>>, List<Map<String, Object>>, Exception> selectListFn;
	private Fn<Map<String, List<Object>>, Map<String, Object>, Exception> selectOneFn;
	private Fn<Map<String, List<Object>>, Integer, Exception> insertFn;
	private Fn<Map<String, List<Object>>, Integer, Exception> updateFn;
	private Fn<Map<String, List<Object>>, Integer, Exception> deleteFn;
	
	public GcrudManager2() {
	}
	
	public GcrudManager getManager() {
		return manager;
	}
	public void setManager(GcrudManager manager) {
		this.manager = manager;
	}
	
	/**
	 * {@link getManager()}の{@link GcrudManager#getTableInfoList()}を返します。
	 * @return
	 */
	public List<TableInfo> getTableInfoList() {
		return manager.getTableInfoList();
	}
	
	/**
	 * {@link getManager()}の{@link GcrudManager#getTableInfoMap()}を返します。
	 * @return
	 */
	public Map<String, TableInfo> getTableInfoMap() {
		return manager.getTableInfoMap();
	}
	
	/**
	 * {@link GcrudManager2#getManager()}を利用して CRUD アクセサを生成して保持します。
	 *
	 */
	public void init() {
		selectListFn = FnUtils.join(MAP_CONVERTER, manager.createSelectListFn());
		selectOneFn = FnUtils.join(MAP_CONVERTER, manager.createSelectOneFn());
		insertFn = FnUtils.join(MAP_CONVERTER, manager.createInsertFn());
		updateFn = FnUtils.join(MAP_CONVERTER, manager.createUpdateFn());
		deleteFn = FnUtils.join(MAP_CONVERTER, manager.createDeleteFn());
	}
	
	/**
	 * レコードを登録します。
	 * @param map
	 * @return
	 * @throws Exception 
	 */
	public Integer insert(final Map<String, List<Object>> map) throws Exception {
		return insertFn.exec(map);
	}
	
	/**
	 * レコードを更新します。
	 * @param map
	 * @return
	 * @throws Exception 
	 */
	public Integer update(final Map<String, List<Object>> map) throws Exception {
		return updateFn.exec(map);
	}
	
	/**
	 * レコードを削除します。
	 * @param map
	 * @return
	 * @throws Exception
	 */
	public Integer delete(final Map<String, List<Object>> map) throws Exception {
		return deleteFn.exec(map);
	}
	
	/**
	 * テーブル名にマッチするテーブルのカラム情報を返します。
	 * @param tableName
	 * @return
	 */
	public SingleRowInfo getColumns(final String tableName) {
		final TableInfo table = getTableInfoMap().get(tableName);
		final SingleRowInfo row = new SingleRowInfo();
		row.setTableName(table.getTableName());
		row.setColList(new ArrayList<SingleColumnInfo>());
		for(final String columnName : table.getColNameList()) {
			final SingleColumnInfo column = new SingleColumnInfo();
			column.setColumnName(columnName);
			row.getColList().add(column);
		}
		return row;
	}
	
	/**
	 * テーブル情報とレコード情報をマージして、
	 * 参照制約によるアンカー情報が付いた
	 * レコード情報を生成して返します。
	 * @param tableInfo
	 * @param valueMap
	 * @return
	 */
	public AnchorRowInfo convert(
			final TableInfo tableInfo,
			final Map<String, Object> valueMap) {
		final AnchorRowInfo rowInfo = new AnchorRowInfo();
		rowInfo.setTableName(tableInfo.getTableName());
		rowInfo.setColList(new ArrayList<SingleColumnInfo>());
		for(final String columnName : tableInfo.getColNameList()) {
			final SingleColumnInfo column = new SingleColumnInfo();
			column.setColumnName(columnName);
			column.setValue(valueMap.get(columnName));
			rowInfo.getColList().add(column);
		}

		{
			final SingleRowInfo primaryInfo = new SingleRowInfo();
			primaryInfo.setTableName(tableInfo.getTableName());
			primaryInfo.setColList(new ArrayList<SingleColumnInfo>());
			for (final String s : tableInfo.getPkNameList()) {
				final SingleColumnInfo column = new SingleColumnInfo();
				column.setColumnName(s);
				column.setValue(valueMap.get(s));
				primaryInfo.getColList().add(column);
			}
			rowInfo.setPrimaryInfo(primaryInfo);
		}
		
		rowInfo.setImportedList(new ArrayList<SingleRowInfo>());
		for (final ReferenceInfo refInfo : tableInfo.getImportedKeysList()) {
			final SingleRowInfo tmpRowInfo = new SingleRowInfo();
			tmpRowInfo.setTableName(refInfo.getPkTableName());
			tmpRowInfo.setColList(new ArrayList<SingleColumnInfo>());
			final int size = refInfo.getPkNameList().size();
			for (int i = 0; i < size; i++) {
				final SingleColumnInfo tmpColInfo = new SingleColumnInfo();
				tmpColInfo.setColumnName(refInfo.getPkNameList().get(i));
				tmpColInfo.setValue(valueMap.get(refInfo.getFkNameList().get(i)));
				tmpRowInfo.getColList().add(tmpColInfo);
			}
			rowInfo.getImportedList().add(tmpRowInfo);
		}
		
		rowInfo.setExportedList(new ArrayList<SingleRowInfo>());
		for (final ReferenceInfo refInfo : tableInfo.getExportedKeysList()) {
			final SingleRowInfo tmpRowInfo = new SingleRowInfo();
			tmpRowInfo.setTableName(refInfo.getFkTableName());
			tmpRowInfo.setColList(new ArrayList<SingleColumnInfo>());
			final int size = refInfo.getPkNameList().size();
			for (int i = 0; i < size; i++) {
				final SingleColumnInfo tmpColInfo = new SingleColumnInfo();
				tmpColInfo.setColumnName(refInfo.getFkNameList().get(i));
				tmpColInfo.setValue(valueMap.get(refInfo.getPkNameList().get(i)));
				tmpRowInfo.getColList().add(tmpColInfo);
			}
			rowInfo.getExportedList().add(tmpRowInfo);
		}
		
		return rowInfo;
	}
	
	/**
	 * ひとつのテーブルを検索してレコードの一覧を返します。
	 * @param map
	 * @return
	 * @throws Exception 
	 * @see GcrudManager2#convert(TableInfo, Map)
	 */
	public ListInfo selectList(final Map<String, List<Object>> map) throws Exception {
		final List<Map<String, Object>> valueMapList = selectListFn.exec(map);
		
		final ListInfo listInfo = new ListInfo();
		listInfo.setColumnNameList(new ArrayList<SingleColumnInfo>());
		listInfo.setRowList(new ArrayList<SingleRowInfo>());
		
		listInfo.setTableName(map.get("tableName").get(0).toString());
		final TableInfo tableInfo = getTableInfoMap().get(listInfo.getTableName());
		
		for (final String s : tableInfo.getColNameList()) {
			final SingleColumnInfo column = new SingleColumnInfo();
			column.setColumnName(s);
			column.setValue(s);
			listInfo.getColumnNameList().add(column);
		}
		for (final Map<String, Object> valueMap : valueMapList) {
			listInfo.getRowList().add(convert(tableInfo, valueMap));
		}
		return listInfo;
	}

	/**
	 * ひとつのテーブルを検索してレコードの情報を返します。
	 * @param map
	 * @return
	 * @throws Exception 
	 * @see GcrudManager2#convert(TableInfo, Map)
	 */
	public SingleRowInfo selectOne(final Map<String, List<Object>> map) throws Exception {
		final Map<String, Object> valueMap = selectOneFn.exec(map);
		final TableInfo table = getTableInfoMap().get(map.get("tableName").get(0).toString());
		return convert(table, valueMap);
	}
}
