package org.maachang.report ;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.maachang.util.FileUtil;

/**
 * １つのExcelシートを表すオブジェクト.
 * 
 * @version 2008/09/29
 * @author masahito suzuki
 * @since MaachangReport 1.00
 */
class ExcelSheet {
    
    /**
     * セルタイプ : ブランク(なし).
     */
    public static final int CELL_BLANK = 0 ;
    
    /**
     * セルタイプ : 数字.
     */
    public static final int CELL_NUMBER = 1 ;
    
    /**
     * セルタイプ : 文字.
     */
    public static final int CELL_STRING = 2 ;
    
    /**
     * セルタイプ : Boolean.
     */
    public static final int CELL_BOOLEAN = 3 ;
    
    /**
     * セルタイプ : 日付.
     */
    public static final int CELL_DATE = 4 ;
    
    /**
     * POIワークブック(１つのExcelファイルを表す).
     */
    private HSSFWorkbook work = null ;
    
    /**
     * シート内容.
     */
    private HSSFSheet sheet = null ;
    
    /**
     * シート名.
     */
    private String sheetName = null ;
    
    /**
     * コンストラクタ.
     */
    public ExcelSheet() {
        
    }
    
    /**
     * コンストラクタ.
     * @param name Excelファイル名を設定します.
     * @param sheetName 利用対象のExcelシート名を設定します.
     * @exception Exception 例外.
     */
    public ExcelSheet( String name,String sheetName ) throws Exception {
        this.open( name,sheetName ) ;
    }
    
    /**
     * 帳票雛型のExcelファイルをオープン.
     * @param name Excelファイル名を設定します.
     * @param sheetName 利用対象のExcelシート名を設定します.
     * @exception Exception 例外.
     */
    public void open( String name,String sheetName ) throws Exception {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "ファイル名が設定されていません" ) ;
        }
        if( name.endsWith( ".xls" ) == false ) {
            name += ".xls" ;
        }
        if( FileUtil.isFileExists( name ) == false ) {
            throw new IOException( "指定ファイル[" + name + "]は存在しません" ) ;
        }
        else if( FileUtil.isRead( name ) == false ) {
            throw new IOException( "指定ファイル[" + name + "]は読み込み権限が存在しません" ) ;
        }
        if( sheetName == null || ( sheetName = sheetName.trim() ).length() <= 0 ) {
            sheetName = "Sheet1" ;
        }
        destroy() ;
        InputStream ins = null ;
        try {
            ins = new BufferedInputStream( new FileInputStream( name ) ) ;
            POIFSFileSystem fs = new POIFSFileSystem( ins ) ;
            this.work = new HSSFWorkbook( fs ) ;
            this.sheetName = sheetName ;
            this.sheet = work.getSheet( sheetName ) ;
            ins.close() ;
            ins = null ;
        } catch( Exception e ) {
            destroy() ;
            throw e ;
        } finally {
            if( ins != null ) {
                try {
                    ins.close() ;
                } catch( Exception e ) {
                }
            }
            ins = null ;
        }
    }
    
    /**
     * 帳票破棄.
     */
    public void destroy() {
        work = null ;
        sheet = null ;
        sheetName = null ;
    }
    
    /**
     * ワークシート名を取得.
     * @return String ワークシート名が返されます.
     */
    public String getSheetName() {
        return sheetName ;
    }
    
    /**
     * シートオブジェクトを取得.
     * @return HSSFWorkbook シートオブジェクトが返されます.
     */
    public HSSFSheet getSheet() {
        return sheet ;
    }
    
    /**
     * オブジェクトが有効かチェック.
     * @return boolean [true]の場合、有効です.
     */
    public boolean isUse() {
        return ( work != null ) ;
    }
    
    /**
     * 現在のExcel情報をバイナリで取得.
     * @return byte[] 現在のExcel情報が返されます.
     * @exception Exception 例外.
     */
    public byte[] getBytes() throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        ByteArrayOutputStream bo = null ;
        try {
            bo = new ByteArrayOutputStream() ;
            work.write( bo ) ;
            bo.flush() ;
            byte[] ret = bo.toByteArray() ;
            bo.close() ;
            bo = null ;
            return ret ;
        } finally {
            if( bo != null ) {
                try {
                    bo.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    /**
     * 現在のExcel情報をファイル出力.
     * @param name 出力先のファイル名を設定します.
     * @exception Exception 例外.
     */
    public void output( String name ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        OutputStream bo = null ;
        try {
            bo = new BufferedOutputStream( new FileOutputStream( name ) ) ;
            work.write( bo ) ;
            bo.flush() ;
            bo.close() ;
            bo = null ;
        } finally {
            if( bo != null ) {
                try {
                    bo.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    /**
     * １つのセル情報に設定.
     * @param x 位置X値を設定します.
     * @param y 位置Y値を設定します.
     * @param data 設定情報をセットします.
     * @exception Exception 例外.
     */
    public void setCell( int x,int y,Object data )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( x <= -1 || x > 32767 || y <= -1 || y > 65535 ) {
            throw new IllegalArgumentException( "指定座標は不正です[x:" + x + " y:" + y + "]" ) ;
        }
        if( data == null ) {
            data = "" ;
        }
        HSSFRow row = sheet.getRow( y ) ;
        if( row == null ) {
            row = sheet.createRow( y ) ;
        }
        HSSFCell cell = row.getCell( (short)x );
        if( cell == null ) {
            cell = row.createCell( ( short )x ) ;
        }
        if( data instanceof String ) {
            //cell.setCellType( HSSFCell.CELL_TYPE_STRING ) ;
            cell.setCellValue( new HSSFRichTextString( ( String )data ) ) ;
        }
        else if( data instanceof Boolean ) {
            //cell.setCellType( HSSFCell.CELL_TYPE_BOOLEAN ) ;
            cell.setCellValue( ( ( Boolean )data ).booleanValue() ) ;
        }
        else if( data instanceof Integer ) {
            //cell.setCellType( HSSFCell.CELL_TYPE_NUMERIC ) ;
            cell.setCellValue( ( ( Integer )data ).intValue() ) ;
        }
        else if( data instanceof Double ) {
            //cell.setCellType( HSSFCell.CELL_TYPE_NUMERIC ) ;
            cell.setCellValue( ( ( Double )data ).doubleValue() ) ;
        }
        else if( data instanceof java.util.Date ) {
            //cell.setCellType( HSSFCell.CELL_TYPE_NUMERIC ) ;
            cell.setCellValue( ( ( java.util.Date )data ) ) ;
        }
        else {
            throw new IllegalArgumentException(
                "指定オブジェクトタイプ[" + data.getClass().getName() + "]は対応していません" ) ;
        }
    }
    
    /**
     * １つのセル情報に設定.
     * @param x 位置X値(ExcelA-Z座標)を設定します.
     * @param y 位置Y値を設定します.
     * @param data 設定情報をセットします.
     * @exception Exception 例外.
     */
    public void setCell( String x,int y,Object data )
        throws Exception {
        setCell( ExcelUtil.getPosAz( x ),y,data ) ;
    }
    
    /**
     * １つのセル内容を取得.
     * @param x 位置X値を設定します.
     * @param y 位置Y値を設定します.
     * @return Object セル内容が返されます.
     * @exception Exception 例外.
     */
    public Object getCell( int x,int y )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( x <= -1 || x > 32767 || y <= -1 || y > 65535 ) {
            throw new IllegalArgumentException( "指定座標は不正です[x:" + x + " y:" + y + "]" ) ;
        }
        HSSFRow row = sheet.getRow( y ) ;
        if( row == null ) {
            throw new IllegalArgumentException( "Y座標[" + y + "]の取得に失敗しました" ) ;
        }
        HSSFCell cell = row.getCell( (short)x );
        if( cell == null ) {
            throw new IllegalArgumentException( "座標[x:" + x + " y:" + y + "]の取得に失敗しました" ) ;
        }
        Object ret = null ;
        int type = cell.getCellType() ;
        switch( type ) {
            case HSSFCell.CELL_TYPE_BLANK  :
                ret = "" ;
                break ;
            case HSSFCell.CELL_TYPE_NUMERIC :
                if( HSSFDateUtil.isCellDateFormatted( cell ) ) {
                    ret = cell.getDateCellValue() ;
                }
                else {
                    ret = cell.getNumericCellValue() ;
                }
                break ;
            case HSSFCell.CELL_TYPE_STRING :
                HSSFRichTextString tx = cell.getRichStringCellValue() ;
                if( tx == null ) {
                    ret = "" ;
                }
                else {
                    ret = tx.getString() ;
                }
                break ;
            case HSSFCell.CELL_TYPE_BOOLEAN :
                ret = cell.getBooleanCellValue() ;
                break ;
        }
        return ret ;
    }
    
    /**
     * １つのセル内容を取得.
     * @param x 位置X(ExcelA-Z座標)を設定します.
     * @param y 位置Y値を設定します.
     * @return Object セル内容が返されます.
     * @exception Exception 例外.
     */
    public Object getCell( String x,int y )
        throws Exception {
        return getCell( ExcelUtil.getPosAz( x ),y ) ;
    }
    
    /**
     * １つのセルタイプを取得.
     * @param x 位置X値を設定します.
     * @param y 位置Y値を設定します.
     * @return int セルタイプが返されます.
     *             [-1]の場合、取得に失敗したか、不明なタイプです.
     */
    public int getCellType( int x,int y ) {
        if( isUse() == false ) {
            return -1 ;
        }
        if( x <= -1 || x > 32767 || y <= -1 || y > 65535 ) {
            return -1 ;
        }
        HSSFRow row = sheet.getRow( y ) ;
        if( row == null ) {
            return -1 ;
        }
        HSSFCell cell = row.getCell( (short)x );
        if( cell == null ) {
            return -1 ;
        }
        int type = cell.getCellType() ;
        switch( type ) {
            case HSSFCell.CELL_TYPE_BLANK  :
                return CELL_BLANK ;
            case HSSFCell.CELL_TYPE_NUMERIC :
                if( HSSFDateUtil.isCellDateFormatted( cell ) ) {
                    return CELL_DATE ;
                }
                else {
                    return CELL_NUMBER ;
                }
            case HSSFCell.CELL_TYPE_STRING :
                return CELL_STRING ;
            case HSSFCell.CELL_TYPE_BOOLEAN :
                return CELL_BOOLEAN ;
        }
        return -1 ;
    }
    
    /**
     * １つのセルタイプを取得.
     * @param x 位置X(ExcelA-Z座標)を設定します.
     * @param y 位置Y値を設定します.
     * @return int セルタイプが返されます.
     *             [-1]の場合、取得に失敗したか、不明なタイプです.
     */
    public Object getCellType( String x,int y ) {
        return getCellType( ExcelUtil.getPosAz( x ),y ) ;
    }
    
    /**
     * 指定Row対して、セル追加.
     * @param y 追加対象のY軸を設定します.
     * @param n 追加
     * @exception Exception 例外.
     */
    public void pushRow( int y,int n ) throws Exception {
        if( y <= -1 || y >= 65536 ) {
            throw new IllegalArgumentException( "指定座標は不正です[y:" + y + "]" ) ;
        }
        sheet.shiftRows( y,sheet.getLastRowNum(),n ) ;
    }
    
    /**
     * 挿入可能なセル情報一覧を取得.
     * @param csList セルスタイルリストを設定します.
     * @return CellPositionList 挿入可能なセルポジションリストが返されます.
     * @exception Exception 例外.
     */
    public CellPositionList getCellPositionList( CellStyleList csList )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        CellPositionList ret = new CellPositionList( csList ) ;
        Iterator it = sheet.rowIterator() ;
        while( it.hasNext() ) {
            HSSFRow row = ( HSSFRow )it.next() ;
            if( row != null ) {
                Iterator it2 = row.iterator() ;
                while( it2.hasNext() ) {
                    HSSFCell cell = ( HSSFCell )it2.next() ;
                    if( cell != null ) {
                        HSSFRichTextString tx = cell.getRichStringCellValue() ;
                        if( tx != null ) {
                            String s = tx.toString() ;
                            if( s != null && ( s = s.trim() ).length() > 0 ) {
                                if( ( s.startsWith( "${" ) && s.endsWith( "}" ) ) ||
                                    ( s.startsWith( "$(" ) && s.endsWith( ")" ) ) ) {
                                    if( s.startsWith( "${" ) ) {
                                        s = s.substring( "${".length(),s.length()-"}".length() ).trim() ;
                                    }
                                    else {
                                        s = s.substring( "$(".length(),s.length()-")".length() ).trim() ;
                                    }
                                    int x = ( int )( cell.getCellNum() & 0x0000ffff ) ;
                                    int y = row.getRowNum() ;
                                    CellPosition pos = new CellPosition( x,y,s ) ;
                                    ret.put( pos ) ;
                                    cell.setCellValue( new HSSFRichTextString("") ) ;
                                }
                            }
                        }
                    }
                }
            }
        }
        return ret ;
    }
    
    
}

