package org.maachang.rimdb.table ;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.maachang.rimdb.RimDbException;
import org.maachang.rimdb.Row;
import org.maachang.rimdb.index.position.PositionIndex;
import org.maachang.rimdb.util.ConvertUtil;
import org.maachang.rimdb.util.NOChild;
import org.maachang.rimdb.util.NOKeyValue;

/**
 * コピー行オブジェクト.
 * 
 * @version 2014/07/07
 * @author  masahito suzuki
 * @since   rimdb-1.00
 */
@SuppressWarnings("unchecked")
final class CopyRow implements Row {
    
    protected final Columns columns ;
    protected final int[] positionColumns ;
    protected final int position ;
    
    private NOKeyValue<Object> newInfo ;
    
    /**
     * コンストラクタ.
     * @param impl コピー対象のオブジェクトを設定します.
     */
    protected CopyRow( Columns columns,int[] positionColumns,int position ) {
        this.columns = columns ;
        this.positionColumns = positionColumns ;
        this.position = position ;
    }
    
    /**
     * 情報クリア.
     * 行ポジションがクリアされます.
     */
    public final void clear() {
        newInfo = null ;
    }
    
    /**
     * 現在のオブジェクトをコピー.
     * @return Row コピーされたオブジェクトが返却されます.
     */
    public final Row copy() {
        NOChild ch ;
        Row ret = new CopyRow( columns,positionColumns,position ) ;
        if( newInfo != null ) {
            newInfo.reset() ;
            while( newInfo.hasNext() ) {
                ch = newInfo.nextChild() ;
                ret.put( ch.getKey(),ch.getValue() ) ;
            }
        }
        return ret ;
    }
    
    /**
     * 行ポジションを取得.
     * @return int ポジションが返却されます.
     */
    public final int position() {
        return position ;
    }
    
    /**
     * カラムタイプを取得.
     * @param no 対象のカラム項番を設定します.
     * @return int カラムタイプが返却されます.
     */
    public final int getType( final int no ) {
        return columns.getType( no ) ;
    }
    
    /**
     * カラムタイプを取得.
     * @param name 対象のカラム名を設定します.
     * @return int カラムタイプが返却されます.
     *             [-1]の場合、取得に失敗しました.
     */
    public final int getType( final String name ) {
        return columns.getType( name ) ;
    }
    
    /**
     * カラム数を取得.
     * @return int カラム数が返却されます.
     */
    public final int length() {
        return columns.length ;
    }
    
    /**
     * カラム名を取得.
     * @param no 対象のカラム項番を設定します.
     * @return String カラム名が返却されます.
     */
    public final String getColumnName( final int no ) {
        return columns.columnNames[ no ] ;
    }
    
    /**
     * 情報取得.
     * @param no 対象の項番を設定します.
     * @return Object 情報が返却されます.
     */
    public final Object getValue( final int no ) {
        if( newInfo == null ) {
            return columns.list[no].get( position ) ;
        }
        final NOChild ret = newInfo.getChild( no ) ;
        if( ret == null ) {
            return columns.list[no].get( position ) ;
        }
        return ret.getValue() ;
    }
    
    /**
     * 情報取得.
     * @param no 対象の項番を設定します.
     * @return Object 情報が返却されます.
     */
    public final Object getValue( final long no ) {
        if( newInfo == null ) {
            return columns.list[(int)no].get( position ) ;
        }
        final NOChild ret = newInfo.getChild( (int)no ) ;
        if( ret == null ) {
            return columns.list[(int)no].get( position ) ;
        }
        return ret.getValue() ;
    }
    
    /**
     * 情報取得.
     * @param name 対象のカラム名を設定します.
     * @return Object 情報が返却されます.
     */
    public final Object getValue( final String name ) {
        if( name == null ) {
            return null ;
        }
        final int p = columns.getColumnNo( name ) ;
        if( p == -1 ) {
            return null ;
        }
        if( newInfo == null ) {
            return columns.list[p].get( position ) ;
        }
        final NOChild ret = newInfo.getChild( p ) ;
        if( ret == null ) {
            return columns.list[p].get( position ) ;
        }
        return ret.getValue() ;
    }
    
    /**
     * 情報取得.
     * @param o 対象の条件を設定します.
     * @return Object オブジェクトが返却されます.
     */
    public final Object get( final Object o ) {
        if( o == null ) {
            return null ;
        }
        else if( o instanceof String ) {
            return getValue( (String)o ) ;
        }
        else if( o instanceof Number ) {
            return getValue( ((Number)o).intValue() ) ;
        }
        else if( ConvertUtil.isNumeric( o ) ) {
            return getValue( ConvertUtil.convertInt( o ) ) ;
        }
        return getValue( ConvertUtil.convertString( o ) ) ;
    }
    
    /**
     * boolean情報を取得.
     * @param no 対象の項番を設定します.
     * @return Boolean 情報が返却されます.
     */
    public final Boolean getBoolean( final int no ) {
        return ConvertUtil.convertBool( getValue( no ) ) ;
    }
    
    /**
     * int情報を取得.
     * @param no 対象の項番を設定します.
     * @return Integer 情報が返却されます.
     */
    public final Integer getInt( final int no ) {
        return ConvertUtil.convertInt( getValue( no ) ) ;
    }
    
    /**
     * long情報を取得.
     * @param no 対象の項番を設定します.
     * @return Long 情報が返却されます.
     */
    public final Long getLong( final int no ) {
        return ConvertUtil.convertLong( getValue( no ) ) ;
    }
    
    /**
     * float情報を取得.
     * @param no 対象の項番を設定します.
     * @return Float 情報が返却されます.
     */
    public final Float getFloat( final int no ) {
        return ConvertUtil.convertFloat( getValue( no ) ) ;
    }
    
    /**
     * double情報を取得.
     * @param no 対象の項番を設定します.
     * @return Double 情報が返却されます.
     */
    public final Double getDouble( final int no ) {
        return ConvertUtil.convertDouble( getValue( no ) ) ;
    }
    
    /**
     * String情報を取得.
     * @param no 対象の項番を設定します.
     * @return String 情報が返却されます.
     */
    public final String getString( final int no ) {
        return ConvertUtil.convertString( getValue( no ) ) ;
    }
    
    /**
     * Date情報を取得.
     * @param no 対象の項番を設定します.
     * @return Date 情報が返却されます.
     */
    public final java.sql.Date getDate( final int no ) {
        return ConvertUtil.convertSqlDate( getValue( no ) ) ;
    }
    
    /**
     * Time情報を取得.
     * @param no 対象の項番を設定します.
     * @return Time 情報が返却されます.
     */
    public final java.sql.Time getTime( final int no ) {
        return ConvertUtil.convertSqlTime( getValue( no ) ) ;
    }
    
    /**
     * Timestamp情報を取得.
     * @param no 対象の項番を設定します.
     * @return Timestamp 情報が返却されます.
     */
    public final java.sql.Timestamp getTimestamp( final int no ) {
        return ConvertUtil.convertSqlTimestamp( getValue( no ) ) ;
    }
    
    /**
     * boolean情報を取得.
     * @param no 対象の項番を設定します.
     * @return Boolean 情報が返却されます.
     */
    public final Boolean getBoolean( final long no ) {
        return ConvertUtil.convertBool( getValue( no ) ) ;
    }
    
    /**
     * int情報を取得.
     * @param no 対象の項番を設定します.
     * @return Integer 情報が返却されます.
     */
    public final Integer getInt( final long no ) {
        return ConvertUtil.convertInt( getValue( no ) ) ;
    }
    
    /**
     * long情報を取得.
     * @param no 対象の項番を設定します.
     * @return Long 情報が返却されます.
     */
    public final Long getLong( final long no ) {
        return ConvertUtil.convertLong( getValue( no ) ) ;
    }
    
    /**
     * float情報を取得.
     * @param no 対象の項番を設定します.
     * @return Float 情報が返却されます.
     */
    public final Float getFloat( final long no ) {
        return ConvertUtil.convertFloat( getValue( no ) ) ;
    }
    
    /**
     * double情報を取得.
     * @param no 対象の項番を設定します.
     * @return Double 情報が返却されます.
     */
    public final Double getDouble( final long no ) {
        return ConvertUtil.convertDouble( getValue( no ) ) ;
    }
    
    /**
     * String情報を取得.
     * @param no 対象の項番を設定します.
     * @return String 情報が返却されます.
     */
    public final String getString( final long no ) {
        return ConvertUtil.convertString( getValue( no ) ) ;
    }
    
    /**
     * Date情報を取得.
     * @param no 対象の項番を設定します.
     * @return Date 情報が返却されます.
     */
    public final java.sql.Date getDate( final long no ) {
        return ConvertUtil.convertSqlDate( getValue( no ) ) ;
    }
    
    /**
     * Time情報を取得.
     * @param no 対象の項番を設定します.
     * @return Time 情報が返却されます.
     */
    public final java.sql.Time getTime( final long no ) {
        return ConvertUtil.convertSqlTime( getValue( no ) ) ;
    }
    
    /**
     * Timestamp情報を取得.
     * @param no 対象の項番を設定します.
     * @return Timestamp 情報が返却されます.
     */
    public final java.sql.Timestamp getTimestamp( final long no ) {
        return ConvertUtil.convertSqlTimestamp( getValue( no ) ) ;
    }
    
    /**
     * boolean情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Boolean 情報が返却されます.
     */
    public final Boolean getBoolean( final String key ) {
        return ConvertUtil.convertBool( getValue( key ) ) ;
    }
    
    /**
     * int情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Integer 情報が返却されます.
     */
    public final Integer getInt( final String key ) {
        return ConvertUtil.convertInt( getValue( key ) ) ;
    }
    
    /**
     * long情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Long 情報が返却されます.
     */
    public final  Long getLong( final String key ) {
        return ConvertUtil.convertLong( getValue( key ) ) ;
    }
    
    /**
     * float情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Float 情報が返却されます.
     */
    public final Float getFloat( final String key ) {
        return ConvertUtil.convertFloat( getValue( key ) ) ;
    }
    
    /**
     * double情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Double 情報が返却されます.
     */
    public final Double getDouble( final String key ) {
        return ConvertUtil.convertDouble( getValue( key ) ) ;
    }
    
    /**
     * String情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return String 情報が返却されます.
     */
    public final String getString( final String key ) {
        return ConvertUtil.convertString( getValue( key ) ) ;
    }
    
    /**
     * Date情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Date 情報が返却されます.
     */
    public final java.sql.Date getDate( final String key ) {
        return ConvertUtil.convertSqlDate( getValue( key ) ) ;
    }
    
    /**
     * Time情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Time 情報が返却されます.
     */
    public final java.sql.Time getTime( final String key ) {
        return ConvertUtil.convertSqlTime( getValue( key ) ) ;
    }
    
    /**
     * Timestamp情報を取得.
     * @param key 対象のカラム名を設定します.
     * @return Timestamp 情報が返却されます.
     */
    public final java.sql.Timestamp getTimestamp( final String key ) {
        return ConvertUtil.convertSqlTimestamp( getValue( key ) ) ;
    }
    
    /**
     * 空間インデックスの距離を取得.
     * @param x 中心点X軸を設定します.
     * @param y 中心点Y軸を設定します.
     * @return int 距離が返却されます.
     */
    public final int getPoint( final int x,final int y ) {
        if( positionColumns == null ) {
            throw new RimDbException( "空間インデックスは利用できません" ) ;
        }
        if( newInfo == null ) {
            return PositionIndex.getLine( x,y,
                (Integer)columns.list[positionColumns[0]].get( position ),
                (Integer)columns.list[positionColumns[1]].get( position ) ) ;
        }
        final Object xx,yy ;
        final NOChild nx = newInfo.getChild( positionColumns[0] ) ;
        if( nx == null ) {
            xx = columns.list[positionColumns[0]].get( position ) ;
        }
        else {
            xx = nx.getValue() ;
        }
        final NOChild ny = newInfo.getChild( positionColumns[1] ) ;
        if( ny == null ) {
            yy = columns.list[positionColumns[1]].get( position ) ;
        }
        else {
            yy = ny.getValue() ;
        }
        return PositionIndex.getLine( x,y,
            ConvertUtil.convertInt( xx ),ConvertUtil.convertInt( yy ) ) ;
    }
    
    /**
     * 空間インデックスの距離を取得.
     * @param lat 中心緯度を設定します.
     * @param lon 中心経度を設定します.
     * @return int 距離が返却されます.
     */
    public final int getPoint( final Number lat,final Number lon ) {
        if( positionColumns == null ) {
            throw new RimDbException( "空間インデックスは利用できません" ) ;
        }
        final int x,y ;
        if( lat instanceof Double || lat instanceof Float || lat instanceof BigDecimal ) {
            x = PositionIndex.getLat( lat.doubleValue() ) ;
        }
        else {
            x = lat.intValue() ;
        }
        if( lon instanceof Double || lon instanceof Float || lat instanceof BigDecimal ) {
            y = PositionIndex.getLon( lon.doubleValue() ) ;
        }
        else {
            y = lon.intValue() ;
        }
        return getPoint( x,y ) ;
    }
    
    /*********************** 以下 Map定義 ***********************/
    
    public final void putAll(final Map toMerge) {
        Object n ;
        Iterator it = toMerge.keySet().iterator() ;
        while( it.hasNext() ) {
            n = it.next() ;
            put( n,toMerge.get( n ) ) ;
        }
    }
    public final boolean containsValue(final Object value) {
        final int len = columns.length ;
        if( value == null ) {
            for( int i = 0 ; i < len ; i ++ ) {
                if( getValue( i ) == null ) {
                    return true ;
                }
            }
        }
        else {
            for( int i = 0 ; i < len ; i ++ ) {
                if( value.equals( getValue( i ) ) ) {
                    return true ;
                }
            }
        }
        return false ;
    }
    public final Set entrySet() {
        return null ;
    }
    public final Collection values() {
        final int len = columns.length ;
        final ArrayList ret = new ArrayList() ;
        for( int i = 0 ; i < len ; i ++ ) {
            ret.add( getValue( i ) ) ;
        }
        return ret ;
    }
    public final Object put(final Object name, final Object value) {
        final int p = columns.getColumnNo( ConvertUtil.convertString( name ) ) ;
        if( p == -1 ) {
            return null ;
        }
        if( newInfo == null ) {
            newInfo = new NOKeyValue<Object>( columns.length ) ;
        }
        return newInfo.put( p,value ) ;
    }
    public final boolean containsKey(final Object key) {
        if( key instanceof String ) {
            return columns.getColumnNo( (String)key ) != -1 ;
        }
        else if( key instanceof Number ) {
            int n = ((Number)key).intValue() ;
            return ( n >= 0 && n < columns.length ) ;
        }
        return false ;
    }
    public final Object remove(final Object key) {
        return put( key,null ) ;
    }
    public final boolean isEmpty() {
        return false ;
    }
    public final Set keySet() {
        Set<Object> ret = new HashSet<Object>() ;
        getAllKey( ret ) ;
        return ret ;
    }
    public int size() {
        return columns.length ;
    }
    public final void getAllKey( final Set<Object> set ) {
        final int len = columns.length ;
        final String[] names = columns.columnNames ;
        for( int i = 0 ; i < len ; i ++ ) {
            set.add( names[ i ] ) ;
        }
    }
    public final void getAllValues( final Set<Object> set ) {
        final int len = columns.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            set.add( getValue( i ) ) ;
        }
    }
    public final String toString() {
        Object v ;
        final int len = columns.length ;
        final String[] names = columns.columnNames ;
        final StringBuilder buf = new StringBuilder() ;
        
        buf.append( "{" ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                buf.append( "," ) ;
            }
            v = getValue( i ) ;
            if( v instanceof String ) {
                buf.append( names[ i ] ).append( ":\"" ).append( v ).append( "\"" ) ;
            }
            else {
                buf.append( names[ i ] ).append( ":" ).append( v ) ;
            }
        }
        return buf.append( "}" ).toString() ;
    }
}
