<?php

class Geocode
{
    const GPS_BOX_DECIMAL = 10;
    const COUNTRY_LIMITS = [
        'SG' => null, /* no limit! */
        'US' => [
            [
                'name' => 'San Francisco',
                'latitude' => 37.7126152,
                'longitude' => -122.1754642,
                'radius' => 60000, /* in meters */
            ],
        ],
    ];

    public function getAddressById($db, $id)
    {
        $result = array();
        $q = "SELECT a.*,b.timezone AS \"timeZoneId\" FROM address a LEFT JOIN address_timezone b ON (b.id=a.timezone) ";
				$q.= " WHERE a.id=".((int)$id);
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
						$f["address"] = html_entity_decode ($f["address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
            $result = $f;
        }
        return $result;
    }

    public function save($db, $result)
    {
        return Address::save($db, $result);
    }

    public function checkLatLngByAddress($db, $address, $country = GeocodeApi::DEFAULT_COUNTRY_CODE)
    {
        syslog(LOG_WARNING, "Geocode::checkLatLngByAddress(\$db,'${address}',$country)");
        $db_address = pg_escape_string(strtolower($address));
        $db_country = pg_escape_string($country);
        $q = "SELECT a.address, a.latitude AS lat, a.longitude AS lng, b.timezone AS location_start_tz, a.postal, a.timezone, a.id, a.country, b.timezone AS \"timeZoneId\", plus_code ";
        $q .= " FROM address a LEFT JOIN address_timezone b ON b.id=a.timezone LEFT JOIN address_alias c ON a.id=c.address_id ";
        $q .= " WHERE (lower(a.address) = '${db_address}' OR  lower(a.plus_code) = '${db_address}' OR lower(c.name) = '${db_address}') ";
        $q .= " AND a.country='${db_country}' AND a.geocoding_date IS NOT NULL ORDER BY a.geocoding_date DESC LIMIT 1";
        $r = pg_query($db, $q);
        syslog(LOG_WARNING, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
            $f["timeZoneId"] = $f["location_start_tz"]; // Case is lost on pg_query()
            unset($f["location_start_tz"]);
			$f["address"] = html_entity_decode ($f["address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
            return [$f, null];
        }
       
        return [null, pg_last_error($db)];
    }

    public function getTimezone($db, $timezone)
    {
        if (is_int($timezone) && $timezone > 0) {
            return $timezone;
        }
        $db_timezone = pg_escape_string($timezone);
        $q = "SELECT id FROM address_timezone WHERE timezone='${db_timezone}'";
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) {
            return $f[0];
        }
        $q = "INSERT INTO address_timezone (timezone) VALUES('${db_timezone}') RETURNING id";
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) {
            return $f[0];
        }
        return null;
    }

    public function checkDistanceCache($db, $fromLat, $fromLng, $toLat, $toLng)
    {
        $db_fromLat = (float) $fromLat;
        $db_fromLng = (float) $fromLng;
        $db_toLat = (float) $toLat;
        $db_toLng = (float) $toLng;
        $q = "SELECT distance,duration FROM address_distance_cache WHERE ";
        $q .= "start_lat=${db_fromLat} AND start_lng=${db_fromLng} AND end_lat=${db_toLat} AND end_lng=${db_toLng}";
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
            return array($f, null);
        }
        return array(null, pg_last_error($db));
    }

    public function saveDistanceCache($db, $fromLat, $fromLng, $toLat, $toLng, $data)
    {
        $db_fromLat = (float) $fromLat;
        $db_fromLng = (float) $fromLng;
        $db_toLat = (float) $toLat;
        $db_toLng = (float) $toLng;
        $distance = $data["distance"];
        $duration = $data["duration"];
        $q = "INSERT INTO address_distance_cache (start_lat,start_lng,end_lat,end_lng,distance,duration) VALUES (";
        $q .= "${db_fromLat},${db_fromLng},${db_toLat},${db_toLng},${distance},${duration}) RETURNING id";
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
            return array($f, null);
        }
        return array(null, pg_last_error($db));
    }

    public function getTransportProvidersByCountry($db, $country)
    {
        $db_country = pg_escape_string($country);
        $q = "SELECT a.id as app_id,a.transport_provider_id,b.name as transport_provider_name,a.country,a.ios_app_id,a.android_app_id,b.timeout,b.retries
			FROM transport_provider_apps a, transport_providers b
			WHERE a.country='${db_country}' AND b.id=a.transport_provider_id AND b.active=1";
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r)) {
            $result = [];
            while ($f = pg_fetch_assoc($r)) {
                $result[] = $f;
            }
            return array($result, null);
        }
        return array(null, pg_last_error($db));
    }

    public function reverseGeocodeCache($db, $address, $lat, $lng, $postal = null, $country = "SG", $timezone = null,$plus_code="")
    {
        syslog(LOG_WARNING, "Geocode::reverseGeocodeCache(\$db, '$address', $lat, $lng, $postal, $country, $timezone,$plus_code)");
        $result = [
            "lat" => $lat,
            "lng" => $lng,
            "address" => $address,
            "postal" => $postal,
            "country" => $country,
            "timeZoneId" => $timezone,
            "plus_code" => $plus_code,
        ];
        $address = Address::save($db, $result);
        if (!empty($address)) {
            syslog(LOG_WARNING, 'Address cached!');
        } else {
            syslog(LOG_WARNING, 'Failed to cache address!');
        }
    }

    public function reverseGeocode($db, $lat, $lng)
    {
        syslog(LOG_WARNING, "Geocode::reverseGeocode(\$db, $lat, $lng)");
        $db_lat = floatval($lat);
        $db_lng = floatval($lng);
        // Check our local address table with precisions 5 to 3
        for ($i = 5; $i > 2; $i--) {
            $q = "SELECT * FROM address WHERE round(latitude,${i})=round(${db_lat},${i}) AND round(longitude,${i})=round(${db_lng},${i}) ";
            $q .= " AND latitude<>0 AND longitude<>0 ORDER BY geocoding_date DESC LIMIT 1";
            syslog(LOG_WARNING, $q);
            $r = pg_query($db, $q);
            if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
								$f["address"] = html_entity_decode ($f["address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
                return [$f, null];
            }
        }
        // Check our local Singapore table with precisions 5 to 3
        for ($i = 5; $i > 2; $i--) {
            $q = "SELECT * FROM singapore_buildings WHERE round(latitude,${i})=round(${db_lat},${i}) AND round(longitude,${i})=round(${db_lng},${i})";
            $r = pg_query($db, $q);
            if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
                $tz = 1; // Asia/Singapore - since this is "singapore_buildings" table
                self::reverseGeocodeCache($db, $f['address'], $db_lat, $db_lng, $f['postal'], 'SG', $tz);
                return [$f, null];
            }
        }
        return [null, pg_last_error($db)];
    }

    public function reverseGeocodeGps($db, $gpsdb, $lat, $lng, $radius = 100)
    {
        $db_lat = floatval($lat);
        $db_lng = floatval($lng);
        // Check our gps address table with radius
        $r = pg_query_params($gpsdb, self::SELECT_ADDRESS_IN_RADIUS, [
            $db_lng,
            $db_lat,
            $radius,
        ]);
        if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
						$f["address"] = html_entity_decode ($f["address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
            return [$f, null];
        }

        // Check our gps Singapore table with radius
        $r = pg_query_params($gpsdb, self::SELECT_SINGAPORE_BUILDINGS_IN_RADIUS, [
            $db_lng,
            $db_lat,
            $radius,
        ]);
        if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
						$f["address"] = html_entity_decode ($f["address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
            $q = "SELECT * FROM singapore_buildings WHERE id=" . $f["id"];
            $r = pg_query($db, $q);
            $d = pg_fetch_assoc($r);
            $tz = 1; // Asia/Singapore - since this is "singapore_buildings" table
            self::reverseGeocodeCache($db, $f['address'], $db_lat, $db_lng, $d['postal'], 'SG', $tz);
            return [$f, null];
        }

        return [null, pg_last_error($gpsdb)];
    }

    public function mockGPSLocation($db, $member_id, $lat, $lng, $location = 'default')
    {
        syslog(LOG_WARNING, "Geocode::mockGPSLocation(\$db, $member_id, $lat, $lng, $location)");
        $db_member_id = (int) $member_id;
        $db_location = pg_escape_string($location);
        $q = "SELECT lat,lng FROM mock_gps_location WHERE member_id=${db_member_id} AND location='${db_location}'";
        syslog(LOG_WARNING, $q);
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) {
            $lat = $f[0];
            $lng = $f[1];
        } else if ($lat == 0 && $lng == 0) {
            $lat = 1.2966426;
            $lng = 103.7763939;
        }
        return [$lat, $lng];
    }

    public function mockGPSCountry($db, $member_id, $country, $location = 'default')
    {
        syslog(LOG_WARNING, "Geocode::mockGPSCountry(\$db, $member_id, $country, $location)");
        $db_member_id = (int) $member_id;
        $db_location = pg_escape_string($location);
        $q = "SELECT country FROM mock_gps_location WHERE member_id=${db_member_id} AND location='${db_location}'";
        syslog(LOG_WARNING, $q);
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) {
            $country = $f[0];
        } else if ($country == '') {
            $country = 'SG';
        }
        return $country;
    }

    public function getCountry($db, $country)
    {
        $db_country = pg_escape_string(strtolower($country));
        $q = "SELECT * FROM country WHERE lower(code)=lower('${db_country}') OR lower(country)=lower('${db_country}') OR lower(short_name)=lower('${db_country}')";
        $r = pg_query($db, $q);
        if ($r && pg_num_rows($r) && $f = pg_fetch_array($r)) {
            return $f;
        }
        return null;
    }

    public function getCountryByGPS($db, $location, $country, $defaultCountry) {
        syslog(LOG_WARNING,"Geocode::getCountryByGPS(\$db, \$location, $country, $defaultCountry)");
        if ($country!=null && strlen(trim($country))==2) {
            return $country;
        }
        if (is_array($location) && array_key_exists('lat',$location) && array_key_exists('lng',$location)
        && $location['lat']!=0 && $location['lat']!=0) {
            $fromLat = $location['lat'] - Geocode::GPS_BOX_DECIMAL;
            $fromLng = $location['lng'] - Geocode::GPS_BOX_DECIMAL;
            $toLat = $location['lat'] + Geocode::GPS_BOX_DECIMAL;
            $toLng = $location['lng'] + Geocode::GPS_BOX_DECIMAL;
            // First we try address
            $baseQuery = "SELECT country FROM %s WHERE ";
            $baseQuery.= "(latitude BETWEEN ${fromLat} AND ${toLat}) AND (longitude BETWEEN ${fromLng} AND ${toLng})";
            $baseQuery.= " ORDER BY ST_DistanceSphere(%s::geometry, ST_SetSRID(ST_MakePoint(".$location["lng"].",".$location["lat"]."),4326))";
            $q = sprintf($baseQuery, "address", "geometry");
            $q.= " LIMIT 1";
            syslog(LOG_WARNING,$q);
            $r = pg_query($db, $q);
            if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) {
                $country = $f[0];
            } else {
                // Then we try geoname
                $q = sprintf($baseQuery, "geoname", "location");
                $q.= " LIMIT 1";
                syslog(LOG_WARNING,$q);
                $r = pg_query($db, $q);
                if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) {
                    $country = $f[0];
                }
            }
        } else {
            syslog(LOG_WARNING,"No GPS coordinates supplied!");
        }
        if ($country==null || strlen(trim($country))!=2) {
            return $defaultCountry;
        }
        return $country;
    }

    public static function saveGeocodingLog($db, $address, $latitude, $longitude, $address_output,$duration, $client_ip, $client_id)
    {
        $i = "INSERT INTO geocoding_logs (address_input,latitude,longitude,call_dated,address_output, duration, client_ip,client_id) VALUES ('${address}'," . (!empty($latitude)?$latitude: "NULL") . "," . (!empty($longitude)?$longitude: "NULL") . ",NOW(),'${address_output}',${duration},'${client_ip}','${client_id}') RETURNING id";
        $r = pg_query($db, $i);
        syslog(LOG_WARNING,$i);
        if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) {
            $log_id = $f[0];
        }
        return $log_id;
    }

    const SELECT_ADDRESS_IN_RADIUS = "SELECT id,
       address,
       latitude,
       longitude,
       ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1, $2),4326)) AS distance
FROM address
WHERE
     ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1, $2),4326)) BETWEEN 0 AND $3
ORDER BY distance";

    const SELECT_SINGAPORE_BUILDINGS_IN_RADIUS = "SELECT id,
       address,
       latitude,
       longitude,
       ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1, $2),4326)) AS distance
FROM singapore_buildings
WHERE
     ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1, $2),4326)) BETWEEN 0 AND $3
ORDER BY distance";

}

// vi:ts=2
