/*
 * Copyright 2004-2013 the Seasar Foundation and the Others.
 *
 * 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.seasar.extension.dataset.impl;

import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.seasar.extension.dataset.ColumnType;
import org.seasar.extension.dataset.DataColumn;
import org.seasar.extension.dataset.DataRow;
import org.seasar.extension.dataset.DataTable;
import org.seasar.extension.dataset.states.RowStates;
import org.seasar.extension.dataset.types.ColumnTypes;
import org.seasar.extension.jdbc.ColumnNotFoundRuntimeException;
import org.seasar.extension.jdbc.util.ColumnDesc;
import org.seasar.extension.jdbc.util.DatabaseMetaDataUtil;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.util.ArrayMap;
import org.seasar.framework.util.CaseInsensitiveMap;
import org.seasar.framework.util.StringUtil;

/**
 * {@link DataTable}の実装クラスです。
 * 
 * @author higa
 * 
 */
public class DataTableImpl implements DataTable {

    private String tableName;

    private List rows = new ArrayList();

    private List removedRows = new ArrayList();

    private ArrayMap columns = new CaseInsensitiveMap();

    private boolean hasMetaData = false;

    /**
     * {@link DataTableImpl}を作成します。
     * 
     * @param tableName
     */
    public DataTableImpl(String tableName) {
        setTableName(tableName);
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getTableName()
     */
    public String getTableName() {
        return tableName;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#setTableName(java.lang.String)
     */
    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getRowSize()
     */
    public int getRowSize() {
        return rows.size();
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getRow(int)
     */
    public DataRow getRow(int index) {
        return (DataRow) rows.get(index);
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#addRow()
     */
    public DataRow addRow() {
        DataRow row = new DataRowImpl(this);
        rows.add(row);
        row.setState(RowStates.CREATED);
        return row;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getRemovedRowSize()
     */
    public int getRemovedRowSize() {
        return removedRows.size();
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getRemovedRow(int)
     */
    public DataRow getRemovedRow(int index) {
        return (DataRow) removedRows.get(index);
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#removeRows()
     */
    public DataRow[] removeRows() {
        for (int i = 0; i < rows.size();) {
            DataRow row = getRow(i);
            if (row.getState().equals(RowStates.REMOVED)) {
                removedRows.add(row);
                rows.remove(i);
            } else {
                ++i;
            }
        }
        return (DataRow[]) removedRows.toArray(new DataRow[removedRows.size()]);
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getColumnSize()
     */
    public int getColumnSize() {
        return columns.size();
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getColumn(int)
     */
    public DataColumn getColumn(int index) {
        return (DataColumn) columns.get(index);
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getColumn(java.lang.String)
     */
    public DataColumn getColumn(String columnName) {
        DataColumn column = getColumn0(columnName);
        if (column == null) {
            throw new ColumnNotFoundRuntimeException(tableName, columnName);
        }
        return column;
    }

    private DataColumn getColumn0(String columnName) {
        DataColumn column = (DataColumn) columns.get(columnName);
        if (column == null) {
            String name = StringUtil.replace(columnName, "_", "");
            column = (DataColumn) columns.get(name);
            if (column == null) {
                for (int i = 0; i < columns.size(); ++i) {
                    String key = (String) columns.getKey(i);
                    String key2 = StringUtil.replace(key, "_", "");
                    if (key2.equalsIgnoreCase(name)) {
                        column = (DataColumn) columns.get(i);
                        break;
                    }
                }
            }
        }
        return column;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#hasColumn(java.lang.String)
     */
    public boolean hasColumn(String columnName) {
        return getColumn0(columnName) != null;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getColumnName(int)
     */
    public String getColumnName(int index) {
        return getColumn(index).getColumnName();
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getColumnType(int)
     */
    public ColumnType getColumnType(int index) {
        return getColumn(index).getColumnType();
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getColumnType(java.lang.String)
     */
    public ColumnType getColumnType(String columnName) {
        return getColumn(columnName).getColumnType();
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#addColumn(java.lang.String)
     */
    public DataColumn addColumn(String columnName) {
        return addColumn(columnName, ColumnTypes.OBJECT);
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#addColumn(java.lang.String,
     *      org.seasar.extension.dataset.ColumnType)
     */
    public DataColumn addColumn(String columnName, ColumnType columnType) {
        DataColumn column = new DataColumnImpl(columnName, columnType, columns
                .size());
        columns.put(columnName, column);
        return column;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#hasMetaData()
     */
    public boolean hasMetaData() {
        return hasMetaData;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#setupMetaData(java.sql.DatabaseMetaData)
     */
    public void setupMetaData(DatabaseMetaData dbMetaData) {
        Set primaryKeySet = DatabaseMetaDataUtil.getPrimaryKeySet(dbMetaData,
                tableName);
        Map columnMap = DatabaseMetaDataUtil
                .getColumnMap(dbMetaData, tableName);
        for (int i = 0; i < getColumnSize(); ++i) {
            DataColumn column = getColumn(i);
            if (primaryKeySet.contains(column.getColumnName())) {
                column.setPrimaryKey(true);
            } else {
                column.setPrimaryKey(false);
            }
            if (columnMap.containsKey(column.getColumnName())) {
                column.setWritable(true);
                ColumnDesc cd = (ColumnDesc) columnMap.get(column
                        .getColumnName());
                column
                        .setColumnType(ColumnTypes.getColumnType(cd
                                .getSqlType()));
            } else {
                column.setWritable(false);
            }
        }
        hasMetaData = true;
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#setupColumns(java.lang.Class)
     */
    public void setupColumns(Class beanClass) {
        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(beanClass);
        for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) {
            PropertyDesc pd = beanDesc.getPropertyDesc(i);
            addColumn(pd.getPropertyName(), ColumnTypes.getColumnType(pd
                    .getPropertyType()));
        }
    }

    public void copyFrom(Object source) {
        if (source instanceof List) {
            copyFromList((List) source);
        } else {
            copyFromBeanOrMap(source);
        }

    }

    private void copyFromList(List source) {
        for (int i = 0; i < source.size(); ++i) {
            DataRow row = addRow();
            row.copyFrom(source.get(i));
            row.setState(RowStates.UNCHANGED);
        }
    }

    private void copyFromBeanOrMap(Object source) {
        DataRow row = addRow();
        row.copyFrom(source);
        row.setState(RowStates.UNCHANGED);
    }

    public String toString() {
        StringBuffer buf = new StringBuffer(100);
        buf.append(tableName);
        buf.append(":");
        for (int i = 0; i < columns.size(); ++i) {
            buf.append(getColumnName(i));
            buf.append(", ");
        }
        buf.setLength(buf.length() - 2);
        buf.append("\n");
        for (int i = 0; i < rows.size(); ++i) {
            buf.append(getRow(i) + "\n");
        }
        buf.setLength(buf.length() - 1);
        return buf.toString();
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof DataTable)) {
            return false;
        }
        DataTable other = (DataTable) o;
        if (getRowSize() != other.getRowSize()) {
            return false;
        }
        for (int i = 0; i < getRowSize(); ++i) {
            if (!getRow(i).equals(other.getRow(i))) {
                return false;
            }
        }
        if (getRemovedRowSize() != other.getRemovedRowSize()) {
            return false;
        }
        for (int i = 0; i < getRemovedRowSize(); ++i) {
            if (!getRemovedRow(i).equals(other.getRemovedRow(i))) {
                return false;
            }
        }
        return true;
    }
}