<?php
/**
 * Via locationLookup, a location can be looked up by pasting a location
 * string into a search bar. This could, for example be a set of coordinates,
 * an OpenStreetmap URL, an Open Location Code (Pluscode) or a Zoph URL for
 * a photo or location.
 *
 * Zoph is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Zoph is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Zoph; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @author John Lines
 * @author Jeroen Roos
 * @package Zoph
 */

namespace geo;

use photo;
use place;

/**
 * This class describes a point, which is a string containing a latitude and
 * longitute.
 * The latitude and longitude should be the only parts which are exposed.
 *
 * On input a postion could be
 *    51.751944, -1.257778    (a decimal latitude and longitude)
 *
 *  would like to handle
 *    51° 45′ 7″ N, 1° 15′ 28″ W  (Degrees, Minutes, Seconds)
 *    https://www.openstreetmap.org/?mlat=51.751944&mlon=-1.257778&zoom=12#map=12/51.7519/-1.2578
 *    https://www.openstreetmap.org/#map=15/51.7520/-1.2631
 *    geo:51.7520,-1.2631?z=15
 *  as well as various Google, Bing etc map URLs, with the complexities hidden in this module
 *
 * @author John Lines
 * @author Jeroen Roos
 * @package Zoph
 */
class locationLookup  {

    /** @var string string to lookup */
    private $search;
    /** @var string string name of server (needed to recognise local URLs) */
    private $server;
    /** @var float latitude */
    private $lat;
    /** @var float longitude */
    private $long;
    /** @var int zoomlevel */
    private $zoom = null;


    /**
     * Create new locationLookup object
     * @param string string to parse
     */
    public function __construct($search, $server = null) {
        $this->search = $search;
        $this->server = $server;
        $this->parse();
    }

    /**
     * takes a string, which is probably an OpenSteetmap URL, and turn it into
     * a latitude and longitude
     * e.g. #map=9/10.4/-12.92
     */
    private function parseOpenstreetmap() {
        if (preg_match('/^https:\/\/www\.openstreetmap\.org\/#map=(\d+)\/([-]?[\d]*[.]?[\d]*)\/([-]?[\d]*[.]?[\d]*)/', $this->search, $matches)) {
            $this->zoom = $matches[1];
            $this->lat = $matches[2];
            $this->long = $matches[3];
        }
    }

    private function parseOLC() {

        $digits = "23456789CFGHJMPQRVWX";
        $zoomlevels = array(5, 9, 12, 14, 16, 17, 18);

        $olc = $this->search;
        $lat = 0;
        $lon = 0;
        $zoom = 0;

        if (preg_match('/^[2-9CFGHJMPQRVWX]{2,8}0*\+[2-9CFGHJMPQRVWX]{0,3}$/', $olc)) {
            $h = 400;
            $w = 400;
            $olc=str_replace("+", "", $olc);
            for ($i=0; $i<strlen($olc); $i++) {
                $char = substr($olc, $i, 1);

                if ($char == "0") {
                    break;
                }
                if ($i < 10) {
                    $val = strpos($digits, $char);

                    if ($i % 2) {
                        $h /= 20;
                        $lon += ($val * $h);
                        $zoom++;
                    } else {
                        $w /= 20;
                        $lat += ($val * $w);
                    }

                } else {
                    // After the 10th digit the coding changes
                    $subolc = substr($olc, $i);
                    for ($j=0; $j<strlen($subolc); $j++) {
                        $char = substr($subolc, $j, 1);
                        $row = intval(strpos($digits, $char) / 4);
                        $col = strpos($digits, $char) % 4;

                        $h /= 5;
                        $w /= 4;


                        $lat += ($h * $row);
                        $lon += ($w * $col);
                        $zoom++;
                    }
                    break;
                }
            }
            // Return the center point of the found rectangle
            $this->lat = round($lat + ($h / 2) - 90, 7);
            $this->long = round($lon + ($w / 2)- 180, 7);


            $this->zoom = $zoomlevels[$zoom];
        }
    }

    private function parseLocalURL() {
        if (preg_match("/(photo|image).php?.+photo_id=(\d+)/", $this->search, $matches)) {
            $obj = new photo($matches[2]);
        } else if (preg_match("/(place).php?.+place_id=(\d+)/", $this->search, $matches)) {
            $obj = new place($matches[2]);
        } else {
            return false;
        }
        $obj->lookup();
        $this->lat=$obj->get("lat");
        $this->long=$obj->get("lon");
        $this->zoom=$obj->get("mapzoom");
    }

    /**
     * Takes a string which contains a location in any of ways that a location
     * could be specified and sets lat and long appropiately (or to empty if
     * search does not contain a parseable location
     */
    private function parse() {
        // error_log("locationLookup::parse()  called with ".$search." **");

        if (empty($this->search)) {
            $this->lat = "" ;
            $this->long = "";
            //    error_log("locationLookup was empty");
        } else if (strcmp($this->search,',') == 0) {
            /* match empty except a single comma - excpt  preg_match('/[\s]*[,][\s]',$search matched too easity */

            // error_log("locationLookup - single comma");
            $this->search = "";
        } else if (preg_match('/([-]?[\d]*[.]?[\d]*)[,][\s]*([-]?[\d]*[.]?[\d]*)/', $this->search, $matches)) {
            $zoomlevels = array(4,7,10,12,14,15,16,17,1);
            /* match   1.456,-12.793   and similar  */
            // error_log ("locationLookup - a match was found");

            $this->lat = $matches[1];
            $this->long = $matches[2];

            $lat = $this->lat * 1000000;
            $lon = $this->long * 1000000;
            $zoom = 8;
            while ($lat != 0 && $lon !=0) {
                $zoom--;
                $newlat = floor($lat / 10);
                $newlon = floor($lon / 10);
                if ($lat != $newlat * 10 || $lon != $newlon * 10) {
                    break;
                }
                $lat = $newlat;
                $lon = $newlon;
            }
            $this->zoom=$zoomlevels[$zoom];
        } else if (preg_match('/^https:\/\/www\.openstreetmap\.org\/.*/',$this->search)) {
            $this->parseOpenstreetmap();
        } else if (preg_match('/^[2-9CFGHJMPQRVWX]{2,8}0*\ [2-9CFGHJMPQRVWX]{0,3}$/', $this->search)) {
            $this->search=str_replace(" ", "+", $this->search);
            $this->parseOLC();
        } else if (preg_match("/^https?:\/\/" . $this->server . "\//", $this->search)) {
            // URL pointing to server
            $this->parseLocalURL();
        } else {
            /* this is a case we cant handle yet */
            error_log("locationLookup: unhandled case " . $this->search);
        }
    }

    /**
     * return the latitude from this location, or a null string if the location
     * does not contain a latitude
     * @return float latitude
     */
    public function getLat() {
        return $this->lat;
    }

    /**
     * return the longitude from this location, or a null string if the location
     * does not contain a longitude
     * @return float longitude
     */
    public function getLong() {
        return $this->long;
    }

    /**
     * return the zoom level from this location, or a null string if the location
     * does not contain a zoom level
     * @return int zoom
     */
    public function getZoom() {
        return $this->zoom;
    }

}
?>
