<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
 * Google Maps API integration.
 *
 * $Id: Gmap.php 4302 2009-05-01 02:49:41Z kiall $
 *
 * @package    Gmaps
 * @author     Kohana Team
 * @copyright  (c) 2007-2008 Kohana Team
 * @license    http://kohanaphp.com/license.html
 */
class Gmap_Core {
	
	// Map settings
	protected $id;
	protected $options;
	protected $center;
	protected $control;
	protected $overview_control;
	protected $type_control = FALSE;

	// Map types
	protected $types = array();
	protected $default_types = array
	(
		'G_NORMAL_MAP','G_SATELLITE_MAP','G_HYBRID_MAP','G_PHYSICAL_MAP'
	);
	
	// Markers icons
	protected $icons = array();

	// Map markers
	protected $markers = array();

	/**
	 * Set the GMap center point.
	 *
	 * @param string $id HTML map id attribute
	 * @param array $options array of GMap constructor options
	 * @return void
	 */
	public function __construct($id = 'map', $options = NULL)
	{
		// Set map ID and options
		$this->id = $id;
		$this->options = new Gmap_Options((array) $options);
	}

	/**
	 * Return GMap javascript url
	 *
	 * @param   string  API component
	 * @param   array   API parameters
	 * @return  string
	 */
	 public static function api_url($component = 'jsapi', $parameters = NULL, $separator = '&amp;')
	 {
		if (empty($parameters['ie']))
		{
			// Set input encoding to UTF-8
			$parameters['ie'] = 'utf-8';
		}

		if (empty($parameters['oe']))
		{
			// Set ouput encoding to input encoding
			$parameters['oe'] = $parameters['ie'];
		}

		if (empty($parameters['key']))
		{
			// Set the API key last
			$parameters['key'] = Kohana::config('gmaps.api_key');
		}

		return 'http://'.Kohana::config('gmaps.api_domain').'/'.$component.'?'.http_build_query($parameters, '', $separator);
	 }

	/**
	 * Retrieves the latitude and longitude of an address.
	 *
	 * @param string $address address
	 * @return array longitude, latitude
	 */
	public static function address_to_ll($address)
	{
		$lat = NULL;
		$lon = NULL;

		if ($xml = Gmap::address_to_xml($address))
		{
			// Get the latitude and longitude from the Google Maps XML
			// NOTE: the order (lon, lat) is the correct order
			list ($lon, $lat) = explode(',', $xml->Response->Placemark->Point->coordinates);
		}

		return array($lat, $lon);
	}

	/**
	 * Retrieves the XML geocode address lookup.
	 * ! Results of this method are cached for 1 day.
	 *
	 * @param string $address adress
	 * @return object SimpleXML
	 */
	public static function address_to_xml($address)
	{
		static $cache;

		// Load Cache
		if ($cache === NULL) 
		{
			$cache = Cache::instance();
		}
		
		// Address cache key
		$key = 'gmap-address-'.sha1($address);

		if ($xml = $cache->get($key))
		{
			// Return the cached XML
			return simplexml_load_string($xml);
		}
		else
		{
			// Setup the retry counter and retry delay
			$remaining_retries = Kohana::config('gmaps.retries');
			$retry_delay = Kohana::config('gmaps.retry_delay');

			// Set the XML URL
			$xml_url = Gmap::api_url('maps/geo', array('output' => 'xml', 'q' => $address), '&');

			// Disable error reporting while fetching the feed
			$ER = error_reporting(~E_NOTICE);

			// Enter the request/retry loop.
			while ($remaining_retries)
			{
				// Load the XML
				$xml = simplexml_load_file($xml_url);

				if (is_object($xml) AND ($xml instanceof SimpleXMLElement) AND (int) $xml->Response->Status->code === 200)
				{
					// Cache the XML
					$cache->set($key, $xml->asXML(), array('gmaps'), 86400);

					// Since the geocode was successful, theres no need to try again
					$remaining_retries = 0;
				}
				elseif ((int) $xml->Response->Status->code === 620)
				{
					/* Goole is rate limiting us - either we're making too many requests too fast, or
					 * we've exceeded the 15k per 24hour limit. */

					// Reduce the number of remaining retries
					$remaining_retries--;
					if ( ! $remaining_retries)
				 		return FALSE;

				 	// Sleep for $retry_delay microseconds before trying again.
				 	usleep($retry_delay);
				}
				else
				{
					// Invalid XML response
					$xml = FALSE;

					// Dont retry.
					$remaining_retries = 0;
				}
			}

			// Turn error reporting back on
			error_reporting($ER);
		}

		return $xml;
	}

	/**
	 * Returns an image map
	 *
	 * @param mixed $lat latitude or an array of marker points
	 * @param float $lon longitude
	 * @param integer $zoom zoom level (1-19)
	 * @param string $type map type (roadmap or mobile)
	 * @param integer $width map width
	 * @param integer $height map height
	 * @return string
	 */
	public static function static_map($lat = 0, $lon = 0, $zoom = 6, $type = NULL, $width = 300, $height = 300)
	{
		// Valid map types
		$types = array('roadmap', 'mobile');

		// Maximum width and height are 640px
		$width = min(640, abs($width));
		$height = min(640, abs($height));

		$parameters['size'] = $width.'x'.$height;

		// Minimum zoom = 0, maximum zoom = 19
		$parameters['zoom'] = max(0, min(19, abs($zoom)));

		if (in_array($type, $types))
		{
			// Set map type
			$parameters['maptype'] = $type;
		}

		if (is_array($lat))
		{
			foreach ($lat as $_lat => $_lon)
			{
				$parameters['markers'][] = $_lat.','.$_lon;
			}

			$parameters['markers'] = implode('|', $parameters['markers']);
		}
		else
		{
			$parameters['center'] = $lat.','.$lon;
		}

		return Gmap::api_url('staticmap', $parameters);
	}

	/**
	 * Set the GMap center point.
	 *
	 * @chainable
	 * @param float $lat latitude
	 * @param float $lon longitude
	 * @param integer $zoom zoom level (1-19)
	 * @param string $type default map type
	 * @return object
	 */
	public function center($lat, $lon, $zoom = 6, $type = 'G_NORMAL_MAP')
	{
		$zoom = max(0, min(19, abs($zoom)));
		$type = ($type != 'G_NORMAL_MAP' AND in_array($type, $this->default_types, true)) ? $type : 'G_NORMAL_MAP';

		// Set center location, zoom and default map type
		$this->center = array($lat, $lon, $zoom, $type);

		return $this;
	}

	/**
	 * Set the GMap controls size.
	 *
	 * @chainable
	 * @param string $size small or large
	 * @return object
	 */
	public function controls($size = NULL)
	{
		// Set the control type
		$this->control = (strtolower($size) == 'small') ? 'Small' : 'Large';

		return $this;
	}

	/**
	 * Set the GMap overview map.
	 *
	 * @chainable
	 * @param integer $width width
	 * @param integer $height height
	 * @return object
	 */
	public function overview($width = '', $height = '')
	{
		$size = (is_int($width) AND is_int($height)) ? 'new GSize('.$width.','.$height.')' : '';
		$this->overview_control = 'map.addControl(new google.maps.OverviewMapControl('.$size.'));';

		return $this;
	}

	/**
	 * Set the GMap type controls.
	 * by default renders G_NORMAL_MAP, G_SATELLITE_MAP, and G_HYBRID_MAP
	 *
	 * @chainable
	 * @param string $type map type
	 * @param string $action add or remove map type
	 * @return object
	 */
	public function types($type = NULL, $action = 'remove')
	{
		$this->type_control = TRUE;

		if ($type !== NULL AND in_array($type, $this->default_types, true))
		{
			// Set the map type and action
			$this->types[$type] = (strtolower($action) == 'remove') ? 'remove' : 'add';
		}

		return $this;
	}
	
	/**
	 * Create a custom marker icon
	 *
	 * @chainable
	 * @param string $name icon name
	 * @param array $options icon options
	 * @return object
	 */
	public function add_icon($name, array $options)
	{
		// Add a new cusotm icon
		$this->icons[] = new Gmap_Icon($name, $options);

		return $this;
	}

	/**
	 * Set the GMap marker point.
	 *
	 * @chainable
	 * @param float $lat latitude
	 * @param float $lon longitude
	 * @param string $html HTML for info window
	 * @param array $options marker options
	 * @return object
	 */
	public function add_marker($lat, $lon, $html = '', $options = array())
	{
		// Add a new marker
		$this->markers[] = new Gmap_Marker($lat, $lon, $html, $options);

		return $this;
	}

	/**
	 * Render the map into GMap Javascript.
	 *
	 * @param string $template template name
	 * @param array $extra extra fields passed to the template
	 * @return string
	 */
	public function render($template = 'gmaps/javascript', $extra = array())
	{
		// Latitude, longitude, zoom and default map type
		list ($lat, $lon, $zoom, $default_type) = $this->center;

		// Map
		$map = 'var map = new google.maps.Map2(document.getElementById("'.$this->id.'"));';

		// Map controls
		$controls[] = empty($this->control) ? '' : 'map.addControl(new google.maps.'.$this->control.'MapControl());';

		// Map Types
		if ($this->type_control === TRUE)
		{
			if (count($this->types) > 0)
			{
				foreach($this->types as $type => $action)
					$controls[] = 'map.'.$action.'MapType('.$type.');';
			}

			$controls[] = 'map.addControl(new google.maps.MapTypeControl());';
		}

		if ( ! empty($this->overview_control))
			$controls[] = $this->overview_control;

		// Map centering
		$center = 'map.setCenter(new google.maps.LatLng('.$lat.', '.$lon.'), '.$zoom.', '.$default_type.');';
		
		$data = array_merge($extra, array
			(
				'map' => $map,
				'options' => $this->options,
				'controls' => implode("\n", $controls),
				'center' => $center,
				'icons' => $this->icons,
				'markers' => $this->markers,
			));

		// Render the Javascript
		return View::factory($template, $data)->render();
	}

} // End Gmap