/*
 * 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.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;

import org.apache.poi.hssf.record.RowRecord;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFDataFormat;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.seasar.extension.dataset.ColumnType;
import org.seasar.extension.dataset.DataReader;
import org.seasar.extension.dataset.DataRow;
import org.seasar.extension.dataset.DataSet;
import org.seasar.extension.dataset.DataSetConstants;
import org.seasar.extension.dataset.DataTable;
import org.seasar.extension.dataset.types.ColumnTypes;
import org.seasar.framework.exception.IORuntimeException;
import org.seasar.framework.util.Base64Util;
import org.seasar.framework.util.FileInputStreamUtil;
import org.seasar.framework.util.ResourceUtil;
import org.seasar.framework.util.StringUtil;
import org.seasar.framework.util.TimestampConversionUtil;

/**
 * Excel用の {@link DataReader}です。
 * 
 * @author higa
 * @author manhole
 * @author azusa
 */
public class XlsReader implements DataReader, DataSetConstants {

    /**
     * データセットです。
     */
    protected DataSet dataSet;

    /**
     * ワークブックです。
     */
    protected HSSFWorkbook workbook;

    /**
     * データフォーマットです。
     */
    protected HSSFDataFormat dataFormat;

    /**
     * 文字列をトリミングするかどうか
     */
    protected boolean trimString = true;

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param path
     *            パス
     */
    public XlsReader(String path) {
        this(path, true);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param path
     *            パス
     * @param trimString
     *            文字列をトリムするかどうか
     */
    public XlsReader(String path, boolean trimString) {
        this(ResourceUtil.getResourceAsStream(path), trimString);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param dirName
     *            ディレクトリ名
     * @param fileName
     *            ファイル名
     */
    public XlsReader(String dirName, String fileName) {
        this(dirName, fileName, true);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param dirName
     *            ディレクトリ名
     * @param fileName
     *            ファイル名
     * @param trimString
     *            文字列をトリムするかどうか
     */
    public XlsReader(String dirName, String fileName, boolean trimString) {
        this(ResourceUtil.getResourceAsFile(dirName), fileName, trimString);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param dir
     *            ディレクトリ
     * @param fileName
     *            ファイル名
     */
    public XlsReader(File dir, String fileName) {
        this(dir, fileName, true);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param dir
     *            ディレクトリ
     * @param fileName
     *            ファイル名
     * @param trimString
     *            文字列をトリムするかどうか
     */
    public XlsReader(File dir, String fileName, boolean trimString) {
        this(new File(dir, fileName), trimString);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param file
     *            ファイル
     */
    public XlsReader(File file) {
        this(file, true);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param file
     *            ファイル
     * @param trimString
     *            文字列をトリムするかどうか
     */
    public XlsReader(File file, boolean trimString) {
        this(FileInputStreamUtil.create(file), trimString);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param in
     *            入力ストリーム
     */
    public XlsReader(InputStream in) {
        this(in, true);
    }

    /**
     * {@link XlsReader}を作成します。
     * 
     * @param in
     *            入力ストリーム
     * @param trimString
     *            文字列をトリムするかどうか
     */
    public XlsReader(InputStream in, boolean trimString) {
        this.trimString = trimString;
        try {
            workbook = new HSSFWorkbook(in);
        } catch (IOException ex) {
            throw new IORuntimeException(ex);
        }
        dataFormat = workbook.createDataFormat();
        dataSet = new DataSetImpl();
        for (int i = 0; i < workbook.getNumberOfSheets(); ++i) {
            createTable(workbook.getSheetName(i), workbook.getSheetAt(i));
        }
    }

    public DataSet read() {
        return dataSet;
    }

    /**
     * テーブルを作成します。
     * 
     * @param sheetName
     *            シート名
     * @param sheet
     *            シート
     * @return テーブル
     */
    protected DataTable createTable(String sheetName, HSSFSheet sheet) {
        DataTable table = dataSet.addTable(sheetName);
        int rowCount = sheet.getLastRowNum();
        if (rowCount > 0) {
            setupColumns(table, sheet);
            setupRows(table, sheet);
        } else if (rowCount == 0) {
            setupColumns(table, sheet);
        }
        return table;
    }

    /**
     * カラムの情報をセットアップします。
     * 
     * @param table
     *            テーブル
     * @param sheet
     *            シート
     */
    protected void setupColumns(DataTable table, HSSFSheet sheet) {
        HSSFRow nameRow = sheet.getRow(0);
        HSSFRow valueRow = sheet.getRow(1);
        for (int i = 0; i <= Short.MAX_VALUE; ++i) {
            HSSFCell nameCell = nameRow.getCell((short) i);
            if (nameCell == null) {
                break;
            }
            String columnName = nameCell.getRichStringCellValue().getString();
            if (columnName.length() == 0) {
                break;
            }
            HSSFCell valueCell = null;
            if (valueRow != null) {
                for (int j = 1; j <= sheet.getLastRowNum(); j++) {
                    valueCell = sheet.getRow(j).getCell((short) i);
                    if (valueCell != null
                            && !StringUtil.isEmpty(valueCell.toString())) {
                        break;
                    }
                }
            }
            if (valueCell != null) {
                table.addColumn(columnName, getColumnType(valueCell));
            } else {
                table.addColumn(columnName);
            }
        }
    }

    /**
     * シートの行をセットアップします。
     * 
     * @param table
     *            テーブル
     * @param sheet
     *            シート
     */
    protected void setupRows(DataTable table, HSSFSheet sheet) {
        for (int i = 1; i <= RowRecord.MAX_ROW_NUMBER; ++i) {
            HSSFRow row = sheet.getRow(i);
            if (row == null) {
                break;
            }
            setupRow(table, row);
        }
    }

    /**
     * 行をセットアップします。
     * 
     * @param table
     *            テーブル
     * @param row
     *            行
     */
    protected void setupRow(DataTable table, HSSFRow row) {
        DataRow dataRow = table.addRow();
        for (int i = 0; i < table.getColumnSize(); ++i) {
            HSSFCell cell = row.getCell((short) i);
            Object value = getValue(cell);
            dataRow.setValue(i, value);
        }
    }

    /**
     * セルがBase64でフォーマットされているかどうかを返します。
     * 
     * @param cell
     *            セル
     * @return セルがBase64でフォーマットされているかどうか
     */
    public boolean isCellBase64Formatted(HSSFCell cell) {
        HSSFCellStyle cs = cell.getCellStyle();
        short dfNum = cs.getDataFormat();
        return BASE64_FORMAT.equals(dataFormat.getFormat(dfNum));
    }

    /**
     * セルが日付のフォーマットかどうかを返します。
     * 
     * @param cell
     *            セル
     * @return セルが日付のフォーマットかどうか
     */
    public boolean isCellDateFormatted(HSSFCell cell) {
        HSSFCellStyle cs = cell.getCellStyle();
        short dfNum = cs.getDataFormat();
        String format = dataFormat.getFormat(dfNum);
        if (StringUtil.isEmpty(format)) {
            return false;
        }
        if (format.indexOf('/') > 0 || format.indexOf('y') > 0
                || format.indexOf('m') > 0 || format.indexOf('d') > 0) {
            return true;
        }
        return false;
    }

    /**
     * セルの値を返します。
     * 
     * @param cell
     *            セル
     * @return セルの値
     */
    public Object getValue(HSSFCell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
        case HSSFCell.CELL_TYPE_NUMERIC:
            if (isCellDateFormatted(cell)) {
                return TimestampConversionUtil.toTimestamp(cell
                        .getDateCellValue());
            }
            final double numericCellValue = cell.getNumericCellValue();
            if (isInt(numericCellValue)) {
                return new BigDecimal((int) numericCellValue);
            }
            return new BigDecimal(Double.toString(numericCellValue));
        case HSSFCell.CELL_TYPE_STRING:
            String s = cell.getRichStringCellValue().getString();
            if (s != null) {
                s = StringUtil.rtrim(s);
                if (!trimString && s.length() > 1 && s.startsWith("\"")
                        && s.endsWith("\"")) {
                    s = s.substring(1, s.length() - 1);
                }
            }
            if ("".equals(s)) {
                s = null;
            }
            if (isCellBase64Formatted(cell)) {
                return Base64Util.decode(s);
            }
            return s;
        case HSSFCell.CELL_TYPE_BOOLEAN:
            boolean b = cell.getBooleanCellValue();
            return Boolean.valueOf(b);
        default:
            return null;
        }
    }

    /**
     * カラムの型を返します。
     * 
     * @param cell
     *            セル
     * @return カラムの型
     */
    protected ColumnType getColumnType(HSSFCell cell) {
        switch (cell.getCellType()) {
        case HSSFCell.CELL_TYPE_NUMERIC:
            if (isCellDateFormatted(cell)) {
                return ColumnTypes.TIMESTAMP;
            }
            return ColumnTypes.BIGDECIMAL;
        case HSSFCell.CELL_TYPE_BOOLEAN:
            return ColumnTypes.BOOLEAN;
        case HSSFCell.CELL_TYPE_STRING:
            if (isCellBase64Formatted(cell)) {
                return ColumnTypes.BINARY;
            } else if (trimString) {
                return ColumnTypes.STRING;
            } else {
                return ColumnTypes.NOT_TRIM_STRING;
            }
        default:
            return ColumnTypes.STRING;
        }
    }

    /**
     * 整数かどうかを返します。
     * 
     * @param numericCellValue
     *            numericな値
     * @return 整数かどうか
     */
    protected boolean isInt(final double numericCellValue) {
        return ((int) numericCellValue) == numericCellValue;
    }

}
