package cn.edu.whu.boot.common.utils.allocation;

import java.util.Arrays;

/**
 * Geohash 工具类
 */
public class GeohashUtil {
    private static final int[] BITS = {16, 8, 4, 2, 1};

    private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
            '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
            'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};

    // 不同精度的 Geohash 对应的网格宽和高，单位为 km
    // 比如 CELL_SIZES[1] 为长度为 1 的 GeoHash 所对应网格的宽和高
    private static final double[][] CELL_SIZES = {{}, {5000.0, 5000.0}, {1250.0, 625.0}, {156.0, 156.0}, {39.1, 19.5}, {4.89, 4.89}, {1.22, 0.61}, {0.153, 0.153}, {0.0382, 0.0191}, {0.00477, 0.00477}, {0.00119, 0.000596}, {0.000149, 0.000149}, {3.72e-5, 1.86e-5}};

    // 不同精度的 Geohash 对应的网格的面积，单位为 km2
    private static final double[] CELL_AREA;

    private static final int[] BASE_32_IDX;

    static {
        CELL_AREA = new double[CELL_SIZES.length];
        for (int i = 1; i < CELL_SIZES.length; i++) {
            CELL_AREA[i] = CELL_SIZES[i][0] * CELL_SIZES[i][1];
        }

        BASE_32_IDX = new int[BASE_32[BASE_32.length - 1] - BASE_32[0] + 1];
        Arrays.fill(BASE_32_IDX, -500);
        for (int i = 0; i < BASE_32.length; i++) {
            BASE_32_IDX[BASE_32[i] - BASE_32[0]] = i;
        }
    }

    /**
     * 将经纬度编码为指定精度 Geohash 字符串
     *
     * @param longitude
     * @param latitude
     * @param precision
     * @return
     */
    public static String geohashPoint(double longitude, double latitude, int precision) {
        double[] latInterval = {-90.0, 90.0};
        double[] lngInterval = {-180.0, 180.0};

        final StringBuilder geohash = new StringBuilder(precision);
        boolean isEven = true;

        int bit = 0;
        int ch = 0;

        while (geohash.length() < precision) {
            double mid;
            if (isEven) {
                mid = (lngInterval[0] + lngInterval[1]) / 2D;
                if (longitude > mid) {
                    ch |= BITS[bit];
                    lngInterval[0] = mid;
                } else {
                    lngInterval[1] = mid;
                }
            } else {
                mid = (latInterval[0] + latInterval[1]) / 2D;
                if (latitude > mid) {
                    ch |= BITS[bit];
                    latInterval[0] = mid;
                } else {
                    latInterval[1] = mid;
                }
            }

            isEven = !isEven;

            if (bit < 4) {
                bit++;
            } else {
                geohash.append(BASE_32[ch]);
                bit = 0;
                ch = 0;
            }
        }
        return geohash.toString();
    }

    /**
     * 指定 Geohash 精度，获取对应的网格大小
     *
     * @param precision Geohash 精度，即字符串长度
     * @return 网格的大小，格式为 [宽度，高度]
     */
    public static double[] getCellSize(int precision) {
        if (precision < 1 || precision > 12) {
            return null;
        } else {
            return CELL_SIZES[precision];
        }
    }

    /**
     * 根据期望的网格面积，找到对应的 Geohash 精度
     *
     * @param expectedArea 期望的 Geohash 对应的网格面积
     * @return 最合适的 Geohash 精度
     */
    public static int findPrecision(double expectedArea) {
        int last = CELL_AREA.length - 1;
        int i = last;
        while (i > 0 && CELL_AREA[i] <= expectedArea) {
            i--;
        }
        return i == last ? i : i + 1;
    }

    public static int findPrecisionByWidth(double width) {
        int last = CELL_AREA.length - 1;
        int i = last;
        while (i > 0 && CELL_SIZES[i][0] <= width) {
            i--;
        }
        return i == last ? i : i + 1;
    }

    public static int findPrecisionByHeight(double height) {
        int last = CELL_AREA.length - 1;
        int i = last;
        while (i > 0 && CELL_SIZES[i][1] <= height) {
            i--;
        }
        return i == last ? i : i + 1;
    }
}
