package org.maachang.rimdb.index.position ;

import java.lang.reflect.Array;
import java.util.List;
import java.util.Map;

import org.maachang.rimdb.RimDbException;
import org.maachang.rimdb.index.IndexUtil;
import org.maachang.rimdb.index.MaskFlags;
import org.maachang.rimdb.util.ConvertUtil;
import org.maachang.rimdb.util.OList;

/**
 * 空間インデックス.
 * 
 * @version 2014/07/05
 * @author  masahito suzuki
 * @since   rimdb-1.00
 */
@SuppressWarnings("unchecked")
public final class PositionIndex {
    
    /** Y軸インデックス. **/
    protected static final class YLineIndex {
        int[] yLine ;
        int[] yMask ;
    }
    
    /** 精度. **/
    protected final int accuracy ;
    
    /** Xインデックス. **/
    protected final int[] xIndex ;
    
    /** Yインデックス. **/
    protected final int[][] yIndex ;
    
    /** Y行インデックス. **/
    protected final YLineIndex[][] yLineIndex ;
    
    /** ソート用データ. **/
    protected final int[] sortXList ;
    protected final int[] sortYList ;
    
    /**
     * コンストラクタ.
     * @param accuracy 精度を設定します.
     * @param xList X軸のリストを設定します.
     * @param yList Y軸のリストを設定します.
     * @param xIndex xIndexを設定します.
     * @param yIndex yIndexを設定します.
     * @param yLineIndex Y行インデックスを設定します.
     */
    protected PositionIndex( int accuracy,int[] xList,int[] yList,int[] xIndex,int[][] yIndex,YLineIndex[][] yLineIndex ) {
        this.accuracy = accuracy ;
        this.xIndex = xIndex ;
        this.yIndex = yIndex ;
        this.yLineIndex = yLineIndex ;
        this.sortXList = xList ;
        this.sortYList = yList ;
    }
    
    /** 検索処理. **/
    private final void _search( final MaskFlags out,final MaskFlags and,final int x,final int y,final int half ) {
        
        // ANDが指定されていて、AND条件がゼロ件の場合は処理しない.
        if( and != null && and.getListSize() == 0 ) {
            if( out.getListSize() != 0 ) {
                out.clear() ;
            }
            return ;
        }
        
        // 精度計算.
        final int posX = x / accuracy ;
        final int posY = y / accuracy ;
        final int halfPos = half / accuracy ;
        
        int j,xx ;
        int yStartPos ;
        int yEndPos ;
        YLineIndex[] lm ;
        
        // X軸範囲検索.
        final int xPosLeft = posX - halfPos ;
        final int xPosRight = posX + halfPos ;
        final int xStartPos = IndexUtil.searchIntBS( true,xIndex,xPosLeft ) ;
        final int xEndPos = IndexUtil.searchIntBS( false,xIndex,xPosRight ) ;
        
        // 検索範囲の場合.
        if( ( xIndex[ xStartPos ] >= xPosLeft || xIndex[ xEndPos ] >= xPosRight ) &&
            ( xIndex[ xStartPos ] <= xPosLeft || xIndex[ xEndPos ] <= xPosRight ) ) {
            
            // 検索範囲内のY軸範囲をループ検索.
            int[] yy ;
            final int yPosLeft = posY - halfPos ;
            final int yPosRight = posY + halfPos ;
            
            // And検索.
            if( and != null ) {
                
                for( int i = xStartPos ; i <= xEndPos ; i ++ ) {
                    
                    // Y軸の範囲を検索.
                    xx = xIndex[ i ] ;
                    yy = yIndex[ i ] ;
                    yStartPos = IndexUtil.searchIntBS( true,yy,yPosLeft ) ;
                    yEndPos = IndexUtil.searchIntBS( false,yy,yPosRight ) ;
                    
                    // 検索範囲の場合.
                    if( ( yy[ yStartPos ] >= yPosLeft || yy[ yEndPos ] >= yPosRight ) &&
                        ( yy[ yStartPos ] <= yPosLeft || yy[ yEndPos ] <= yPosRight ) ) {
                        
                        // Y軸の検索範囲に対するマスク値を取得.
                        lm = yLineIndex[ i ] ;
                        for( j = yStartPos ; j <= yEndPos ; j ++ ) {
                            
                            // 半径範囲外の条件は取得対象外.
                            if( getLine( posX,posY,xx,yy[ j ] ) <= halfPos ) {
                                out.orMask( lm[ j ].yLine,lm[ j ].yMask ) ;
                            }
                        }
                        
                    }
                }
                
                // 処理結果に対して、Andマスク.
                out.and( and ) ;
            }
            // 通常検索.
            else {
                
                for( int i = xStartPos ; i <= xEndPos ; i ++ ) {
                    
                    // Y軸の範囲を検索.
                    xx = xIndex[ i ] ;
                    yy = yIndex[ i ] ;
                    yStartPos = IndexUtil.searchIntBS( false,yy,yPosLeft ) ;
                    yEndPos = IndexUtil.searchIntBS( false,yy,yPosRight ) ;
                    
                    // 検索範囲の場合.
                    if( ( yy[ yStartPos ] >= yPosLeft || yy[ yEndPos ] >= yPosRight ) &&
                        ( yy[ yStartPos ] <= yPosLeft || yy[ yEndPos ] <= yPosRight ) ) {
                        
                        // Y軸の検索範囲に対するマスク値を取得.
                        lm = yLineIndex[ i ] ;
                        for( j = yStartPos ; j <= yEndPos ; j ++ ) {
                            
                            // 半径範囲外の条件は取得対象外.
                            if( getLine( posX,posY,xx,yy[ j ] ) <= halfPos ) {
                                out.orMask( lm[ j ].yLine,lm[ j ].yMask ) ;
                            }
                        }
                        
                    }
                }
            }
            
        }
    }
    
    /** 検索パラメータ解析. **/
    protected static final int[] convert( final Object word ) {
        if( word == null ) {
            throw new RimDbException( "空間インデックス検索条件にnullは設定できません" ) ;
        }
        else if( word instanceof int[] ) {
            int[] n = (int[])word ;
            if( n.length < 3 ) {
                throw new RimDbException( "空間インデックス検索条件は3つの定義が必要です" ) ;
            }
            return new int[]{ n[0],n[1],n[2] } ;
        }
        else if( word instanceof Integer[] ) {
            Integer[] n = (Integer[])word ;
            if( n.length < 3 ) {
                throw new RimDbException( "空間インデックス検索条件は3つの定義が必要です" ) ;
            }
            return new int[]{ n[0],n[1],n[2] } ;
        }
        else if( word instanceof List ) {
            List n = (List)word ;
            if( n.size() < 3 ) {
                throw new RimDbException( "空間インデックス検索条件は3つの定義が必要です" ) ;
            }
            return convertLatLon( n.get( 0 ),n.get( 1 ),n.get( 2 ) ) ;
        }
        else if( word instanceof Object[] ) {
            Object[] n = (Object[])word ;
            if( n.length < 3 ) {
                throw new RimDbException( "空間インデックス検索条件は3つの定義が必要です" ) ;
            }
            return convertLatLon( n[0],n[1],n[2] ) ;
        }
        else if( word instanceof OList ) {
            OList n = (OList)word ;
            if( n.size() < 3 ) {
                throw new RimDbException( "空間インデックス検索条件は3つの定義が必要です" ) ;
            }
            return convertLatLon( n.get( 0 ),n.get( 1 ),n.get( 2 ) ) ;
        }
        else if( word instanceof Map ) {
            Map n = (Map)word ;
            Object x,y,h ;
            if( ( x = n.get( "x" ) ) == null || ( y = n.get( "y" ) ) == null ||
                ( h = n.get( "half" ) ) == null ) {
                throw new RimDbException( "空間インデックス検索条件は3つの定義[x,y,half]が必要です" ) ;
            }
            return convertLatLon( x,y,h ) ;
        }
        else if( word.getClass().isArray() ) {
            if( Array.getLength( word ) < 3 ) {
                throw new RimDbException( "空間インデックス検索条件は3つの定義が必要です" ) ;
            }
            return convertLatLon( Array.get( word,0 ),Array.get( word,1 ),Array.get( word,2 ) ) ;
        }
        else {
            throw new RimDbException( "空間インデックス検索条件には[" + word.getClass().getName() +
                "]オブジェクトは定義できません" ) ;
        }
    }
    
    /** 小数点の場合の緯度経度メートル変換計算. **/
    private static final int[] convertLatLon( Object o1,Object o2,Object o3 ) {
        int a,b ;
        if( o1 instanceof Double || o1 instanceof Float ) {
            a = getLat( ConvertUtil.convertDouble( o1 ) ) ;
        }
        else {
            a = ConvertUtil.convertInt( o1 ) ;
        }
        if( o2 instanceof Double || o2 instanceof Float ) {
            b = getLon( ConvertUtil.convertDouble( o2 ) ) ;
        }
        else {
            b = ConvertUtil.convertInt( o2 ) ;
        }
        return new int[]{ a,b,ConvertUtil.convertInt( o3 ) } ;
    }
    
    /** 検索処理. **/
    protected final void _search( final MaskFlags out,final MaskFlags and,final Object word ) {
        int[] n = convert( word ) ;
        _search( out,and,n[0],n[1],n[2] ) ;
    }
    
    /**
     * 検索処理.
     * @param out 検索結果を設定するオブジェクトを設定します.
     * @param x 検索X軸を設定します.
     * @param y 検索Y軸を設定します.
     * @parma half 半径を設定します.
     */
    public final void search( final MaskFlags out,final int x,final int y,final int half ) {
        _search( out,null,x,y,half ) ;
    }
    
    /**
     * 検索処理.
     * @param out 検索結果を設定するオブジェクトを設定します.
     * @param and And条件のオブジェクトを設定します.
     * @param x 検索X軸を設定します.
     * @param y 検索Y軸を設定します.
     * @parma half 半径を設定します.
     */
    public final void search( final MaskFlags out,final MaskFlags and,final int x,final int y,final int half ) {
        _search( out,and,x,y,half ) ;
    }
    
    /**
     * 検索処理.
     * @param out 検索結果を設定するオブジェクトを設定します.
     * @param n 検索条件を設定します.
     */
    public final void search( final MaskFlags out,Object n ) {
        _search( out,null,n ) ;
    }
    
    /**
     * 検索処理.
     * @param out 検索結果を設定するオブジェクトを設定します.
     * @param and And条件のオブジェクトを設定します.
     * @param n 検索条件を設定します.
     */
    public final void search( final MaskFlags out,final MaskFlags and,Object n ) {
        _search( out,and,n ) ;
    }
    
    /**
     * ソート要素を取得.
     * @param x 検索X軸を設定します.
     * @param y 検索Y軸を設定します.
     * @return RadiusSort ソート要素が返却されます.
     */
    public final RadiusSort getSort( final int x,final int y ) {
        return new RadiusSort( sortXList,sortYList,x,y ) ;
    }
    
    /**
     * 指定X,Y軸に対する距離を取得.
     * @param ax 地点AのX軸を設定します.
     * @param ay 地点AのY軸を設定します.
     * @param bx 地点BのX軸を設定します.
     * @param by 地点BのY軸を設定します.
     * @return int 距離が返却されます.
     */
    public static final int getLine( final int ax,final int ay,final int bx,final int by ) {
        
        // 精度はあまり高めでないが、高速で近似値を計算できる.
        final int dx,dy ;
        if ( ( dx = ( ax > bx ) ? ax - bx : bx - ax ) < ( dy = ( ay > by ) ? ay - by : by - ay ) ) {
            return ((( dy << 8 ) + ( dy << 3 ) - ( dy << 4 ) - ( dy << 1 ) +
                    ( dx << 7 ) - ( dx << 5 ) + ( dx << 3 ) - ( dx << 1 )) >> 8 );
        }
        else {
            return ((( dx << 8 ) + ( dx << 3 ) - ( dx << 4 ) - ( dx << 1 ) +
                    ( dy << 7 ) - ( dy << 5 ) + ( dy << 3 ) - ( dy << 1 )) >> 8 );
        }
    }
    
    /** 緯度1メートル係数. **/
    protected static final double _latitudeM = 0.0002777778 / 30.8184033702 ;
    
    /** 経度１メートル係数. **/
    public static final double _longitudeM = 0.0002777778 / 25.244958122 ;
    
    /**
     * 緯度をメートル計算.
     * @param lat 緯度を設定します.
     * @return int メートル単位に計算された情報が返却されます.
     */
    public static final int getLat( final double lat ) {
        return (int)( (double)lat / _latitudeM ) ;
    }
    
    /**
     * 経度をメートル計算.
     * @param lon 経度を設定します.
     * @return int メートル単位に計算された情報が返却されます.
     */
    public static final int getLon( final double lon ) {
        return (int)( lon / _longitudeM ) ;
    }
}
