<?php


namespace App\Services;


class GeohashService
{
    private $coding = "0123456789bcdefghjkmnpqrstuvwxyz";
    private $codingMap = array();

    public function Geohash() {
        for($i = 0; $i < 32; $i++) {
            $this->codingMap[substr($this->coding, $i, 1)] = str_pad(decbin($i), 5, "0", STR_PAD_LEFT);
        }
    }

    public function decode($hash) {
        $binary = "";
        $hl = strlen($hash);
        for($i = 0; $i < $hl; $i++) {
            $binary .= $this->codingMap[substr($hash, $i, 1)];
        }

        $bl = strlen($binary);
        $blat = "";
        $blong = "";
        for ($i = 0; $i < $bl; $i++) {
            if ($i%2) {
                $blat = $blat.substr($binary, $i, 1);
            } else {
                $blong = $blong.substr($binary, $i, 1);
            }
        }

        $lat = $this->binDecode($blat, -90, 90);
        $long = $this->binDecode($blong, -180, 180);

        $latErr = $this->calcError(strlen($blat), -90, 90);
        $longErr = $this->calcError(strlen($blong), -180, 180);

        $latPlaces = max(1, -round(log10($latErr))) - 1;
        $longPlaces = max(1, -round(log10($longErr))) - 1;

        $lat = round($lat, $latPlaces);
        $long = round($long, $longPlaces);

        return array($lat,$long);
    }

    public function encode($lat,$long) {
        $plat = $this->precision($lat);
        $latbits = 1;
        $err = 45;
        while($err > $plat) {
            $latbits++;
            $err/= 2;
        }

        $plong = $this->precision($long);
        $longbits = 1;
        $err = 90;
        while($err > $plong) {
            $longbits++;
            $err /= 2;
        }
        $bits = max($latbits,$longbits);
        $longbits = $bits;
        $latbits = $bits;
        $addlong = 1;
        while (($longbits+$latbits) % 5 != 0) {
            $longbits += $addlong;
            $latbits += !$addlong;
            $addlong = !$addlong;
        }
        $blat = $this->binEncode($lat, -90, 90, $latbits);
        $blong = $this->binEncode($long, -180, 180, $longbits);
        $binary = "";
        $uselong = 1;
        while (strlen($blat)+strlen($blong)) {
            if ($uselong) {
                $binary = $binary.substr($blong, 0, 1);
                $blong = substr($blong, 1);
            } else {
                $binary = $binary.substr($blat, 0, 1);
                $blat = substr($blat, 1);
            }
            $uselong = !$uselong;
        }
        $hash = "";
        for ($i = 0; $i < strlen($binary); $i += 5) {
            $n = bindec(substr($binary, $i, 5));
            $hash = $hash . $this->coding[$n];
        }
        return $hash;
    }

    private function calcError($bits, $min, $max) {
        $err = ($max - $min) / 2;
        while ($bits--) {
            $err /= 2;
        }
        return $err;
    }

    private function precision($number) {
        $precision = 0;
        $pt = strpos($number,'.');
        if ($pt!== false) {
            $precision = -(strlen($number) - $pt - 1);
        }
        return pow(10, $precision) / 2;
    }

    private function binEncode($number, $min, $max, $bitcount) {
        if ($bitcount == 0) {
            return "";
        }
        $mid = ($min + $max) / 2;
        if ($number > $mid) {
            return "1" . $this->binEncode($number, $mid, $max, $bitcount - 1);
        } else {
            return "0" . $this->binEncode($number, $min, $mid, $bitcount - 1);
        }
    }

    private function binDecode($binary, $min, $max) {
        $mid = ($min + $max) / 2;

        if (strlen($binary) == 0) {
            return $mid;
        }
        $bit = substr($binary, 0, 1);
        $binary = substr($binary, 1);

        if ($bit == 1) {
            return $this->binDecode($binary, $mid, $max);
        } else {
            return $this->binDecode($binary, $min, $mid);
        }
    }
}
