package com.mogo.realtime.util;

import java.text.DecimalFormat;

/**
 * 莫顿编码
 */
public class MortonCode {

    /**
     * morton 转 经纬度 时的中间常量
     */
    private static final long NDS_180_DEGREES = 0x7fffffff;

    /**
     * morton 转 经纬度 时的中间常量
     */
    private static final long NDS_360_DEGREES = 4294967295L;

    /**
     * morton 转 经纬度 时的中间常量
     */
    private static final long NDS_90_DEGREES = 0x3fffffff;

    /**
     * 经纬度转 morton 时的中间常量
     */
    private static final double RULE_MORTON = Math.pow( 2, 32 ) / 360;

    /**
     * morton 转 经纬度 时的中间常量
     */
    private static final double RULE_MORTON_TO_LONLAT = 360.0 / Math.pow( 2, 32 );

    /**
     * @param lon 经度
     * @param lat 纬度
     * @return long 莫顿码
     */
    public static long wrapEncodeMorton( Double lon, Double lat ) {
        DecimalFormat decimalFormat = new DecimalFormat( "#.######" );
        return encodeMorton( Double.valueOf( decimalFormat.format( lon ) ),
                Double.valueOf( decimalFormat.format( lat ) ) );
    }

    /**
     * 编码 morton code
     *
     * @param lon 经度
     * @param lat 纬度
     * @return long 莫顿码
     */
    public static long encodeMorton( Double lon, Double lat ) {

        long bit = 1L;
        long mortonCode = 0L;
        long x = ( long ) ( lon * RULE_MORTON );
        long y = ( long ) ( lat * RULE_MORTON );

        if ( y < 0 ) {
            y += 0x7FFFFFFF;
        }
        y = y << 1;
        for ( int i = 0; i < 32; i++ ) {
            // x-part
            mortonCode = mortonCode | ( x & bit );
            x = x << 1;
            bit = bit << 1;
            // y-part
            mortonCode = mortonCode | ( y & bit );
            y = y << 1;
            bit = bit << 1;
        }

        return mortonCode;
    }

    /**
     * 将莫顿码解码为坐标
     *
     * @param mortonCode 莫顿码
     * @return 坐标
     */
    public static double[] decodeMorton( long mortonCode ) {
        long[] midPoint = mortonCodeToCoord( mortonCode );
        normalizeCoord( midPoint );
        double[] point = new double[2];

        // 将经纬度长整数转化为 浮点类型
        point[0] = midPoint[0] * RULE_MORTON_TO_LONLAT;
        point[1] = midPoint[1] * RULE_MORTON_TO_LONLAT;
        return point;
    }

    /**
     * 莫顿码分别拆解为 编码后的经纬度长整数
     *
     * @param mortonCode 莫顿码
     * @return 经纬度 长整数
     */
    private static long[] mortonCodeToCoord( long mortonCode ) {
        long bit = 1L;
        long[] longPoint = new long[2];

        for ( int i = 0; i < 32; i++ ) {
            longPoint[0] |= mortonCode & bit;
            mortonCode >>= 1;
            longPoint[1] |= mortonCode & bit;
            bit <<= 1;
        }
        return longPoint;
    }

    /**
     * 对编码后的经纬度长整数进行解码
     *
     * @param midPoint 经纬度 长整数
     */
    private static void normalizeCoord( long[] midPoint ) {
        // if x > 180 degrees, then subtract 360 degrees
        if ( midPoint[0] > NDS_180_DEGREES ) {
            midPoint[0] -=
                    NDS_360_DEGREES + 1;  // add 1 because 0 must be counted as well
        } else if ( midPoint[0] < -NDS_180_DEGREES )  // if x < 180 , x += 360
        {
            midPoint[0] +=
                    NDS_360_DEGREES + 1;  // add 1 because 0 must be counted as well
        }

        // if y > 90 degrees, then subtract 180 degrees
        if ( midPoint[1] > NDS_90_DEGREES ) {
            midPoint[1] -=
                    NDS_180_DEGREES + 1;  // add 1 because 0 must be counted as well
        } else if ( midPoint[1] < -NDS_90_DEGREES )  // if y < 90, y += 180
        {
            midPoint[1] +=
                    NDS_180_DEGREES + 1;  // add 1 because 0 must be counted as well
        }
    }

}
