<?php

namespace onespace\tools\widgets\maps;

use onespace\tools\assets\MapAsset;
use yii\bootstrap\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use yii\web\JsExpression;
use yii\web\View;

/**
 * GoogleMap
 * @property array $customControls Array of custom control definitions in form [ position => id ], where position is the position constant and id is the HTML ID of the element
 * @property array $clientOptions Options to be passed to map initialiser
 * @property array $styleJson Map style array - default is a OneSpace style, set to false for standard Google style
 * @property array $markers List of marker definitions. See https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions for standard attributes - also supports defining infowindow content and an id
 * @property array $markerEvents Events to add to each marker, in format [ event => js code ]
 * @property array $heatmap List of points to heatmap, as an array of [lat => x, lng => y] arrays
 * @property bool $autoContainMarkers Whether to automatically zoom the map to contain the defined markers. Defaults to `true`
 * 
 * @author  Jason Phillips <jason@one-space.co.za>
 */
class GoogleMap extends Widget {
    const CONTROL_POSITION_BOTTOM = 11;
    const CONTROL_POSITION_BOTTOM_CENTER = 11;
    const CONTROL_POSITION_BOTTOM_LEFT = 10;
    const CONTROL_POSITION_BOTTOM_RIGHT = 12;
    const CONTROL_POSITION_CENTER = 13;
    const CONTROL_POSITION_LEFT = 5;
    const CONTROL_POSITION_LEFT_BOTTOM = 6;
    const CONTROL_POSITION_LEFT_CENTER = 4;
    const CONTROL_POSITION_LEFT_TOP = 5;
    const CONTROL_POSITION_RIGHT = 7;
    const CONTROL_POSITION_RIGHT_BOTTOM = 9;
    const CONTROL_POSITION_RIGHT_CENTER = 8;
    const CONTROL_POSITION_RIGHT_TOP = 7;
    const CONTROL_POSITION_TOP = 2;
    const CONTROL_POSITION_TOP_CENTER = 2;
    const CONTROL_POSITION_TOP_LEFT = 1;
    const CONTROL_POSITION_TOP_RIGHT = 3;

    const MS_MARKER_COLOUR_YELLOW = 'yellow';
    const MS_MARKER_COLOUR_BLUE = 'blue';
    const MS_MARKER_COLOUR_GREEN = 'green';
    const MS_MARKER_COLOUR_LIGHTBLUE = 'lightblue';
    const MS_MARKER_COLOUR_ORANGE = 'orange';
    const MS_MARKER_COLOUR_PINK = 'pink';
    const MS_MARKER_COLOUR_PURPLE = 'purple';
    const MS_MARKER_COLOUR_RED = 'red';
    const MS_MARKER_DOT = '-dot';
    const MS_MARKER_NO_DOT = '';

    public $customControls = [];
    public $clientOptions = [];
    public $styleJson = '';
    public $markers = [];
    public $markerEvents = [];
    public $heatmap = [];
    public $autoContainMarkers = true;

    public function init() {
        parent::init();
        if (!isset($this->options['id'])) {
            $this->options['id'] = $this->getId();
        }

        MapAsset::register($this->view);

        if ($this->styleJson == null && $this->styleJson !== false) {
            $this->styleJson = '[
                {
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#d9e7e7"
                    }
                    ]
                },
                {
                    "elementType": "labels.text.fill",
                    "stylers": [
                    {
                        "color": "#474847"
                    }
                    ]
                },
                {
                    "elementType": "labels.text.stroke",
                    "stylers": [
                    {
                        "color": "#fafeff"
                    }
                    ]
                },
                {
                    "featureType": "administrative",
                    "elementType": "geometry.stroke",
                    "stylers": [
                    {
                        "color": "#cdd6d5"
                    }
                    ]
                },
                {
                    "featureType": "administrative.land_parcel",
                    "elementType": "geometry.stroke",
                    "stylers": [
                    {
                        "color": "#cdd6d5"
                    }
                    ]
                },
                {
                    "featureType": "administrative.land_parcel",
                    "elementType": "labels.text.fill",
                    "stylers": [
                    {
                        "color": "#9ca09c"
                    }
                    ]
                },
                {
                    "featureType": "landscape.natural",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#c1d9d9"
                    }
                    ]
                },
                {
                    "featureType": "poi",
                    "stylers": [
                    {
                        "visibility": "off"
                    }
                    ]
                },
                {
                    "featureType": "poi",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#c1d9d9"
                    }
                    ]
                },
                {
                    "featureType": "poi",
                    "elementType": "labels.text.fill",
                    "stylers": [
                    {
                        "color": "#93817c"
                    }
                    ]
                },
                {
                    "featureType": "poi.park",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "visibility": "on"
                    }
                    ]
                },
                {
                    "featureType": "poi.park",
                    "elementType": "geometry.fill",
                    "stylers": [
                    {
                        "color": "#8f9aa3"
                    }
                    ]
                },
                {
                    "featureType": "poi.park",
                    "elementType": "labels.text.fill",
                    "stylers": [
                    {
                        "color": "#41426c"
                    }
                    ]
                },
                {
                    "featureType": "poi.place_of_worship",
                    "stylers": [
                    {
                        "visibility": "off"
                    }
                    ]
                },
                {
                    "featureType": "road",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#fafeff"
                    }
                    ]
                },
                {
                    "featureType": "road.arterial",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#eef0f1"
                    }
                    ]
                },
                {
                    "featureType": "road.highway",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#aedddc"
                    }
                    ]
                },
                {
                    "featureType": "road.highway",
                    "elementType": "geometry.stroke",
                    "stylers": [
                    {
                        "color": "#83b4b3"
                    }
                    ]
                },
                {
                    "featureType": "road.highway.controlled_access",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#7bb2be"
                    }
                    ]
                },
                {
                    "featureType": "road.highway.controlled_access",
                    "elementType": "geometry.stroke",
                    "stylers": [
                    {
                        "color": "#79a1a9"
                    }
                    ]
                },
                {
                    "featureType": "road.local",
                    "elementType": "labels.text.fill",
                    "stylers": [
                    {
                        "color": "#777978"
                    }
                    ]
                },
                {
                    "featureType": "transit.line",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#c1d9d9"
                    }
                    ]
                },
                {
                    "featureType": "transit.line",
                    "elementType": "labels.text.fill",
                    "stylers": [
                    {
                        "color": "#7b9388"
                    }
                    ]
                },
                {
                    "featureType": "transit.line",
                    "elementType": "labels.text.stroke",
                    "stylers": [
                    {
                        "color": "#d9e7e7"
                    }
                    ]
                },
                {
                    "featureType": "transit.station",
                    "elementType": "geometry",
                    "stylers": [
                    {
                        "color": "#c1d9d9"
                    }
                    ]
                },
                {
                    "featureType": "water",
                    "elementType": "geometry.fill",
                    "stylers": [
                    {
                        "color": "#8eafab"
                    }
                    ]
                },
                {
                    "featureType": "water",
                    "elementType": "labels.text.fill",
                    "stylers": [
                    {
                        "color": "#92998d"
                    }
                    ]
                }
            ]';
        }

        $clientOptions = Json::encode(
            ArrayHelper::merge(
                [
                    'zoom' => 7,
                    'center' => ['lat' => -28.280564, 'lng' => 27.908214],
                    'gestureHandling' => 'greedy',
                    'styles' => json_decode($this->styleJson)
                ],
                $this->clientOptions
            )
        );

        $mapJsVar = self::getJsMapVar($this->getId());
        $this->getView()->registerJs(<<<JS
            $mapJsVar = new google.maps.Map(document.getElementById('{$this->getId()}'), $clientOptions);
JS, View::POS_LOAD);
        $controlString = '';
        foreach ($this->customControls as $position => $controlId) {
            $controlString .= "{$mapJsVar}.controls[{$position}].push(document.getElementById(\"{$controlId}\"));\n";
        }

        if ($controlString != null) {
            $this->getView()->registerJs($controlString, View::POS_LOAD);
        }


        // define markers
        if ($this->markers != null) {
            $markerJsVar = self::getJsMarkersVar($this->getId());
            $spiderfierJsVar = self::getJsVar($this->getId(), 'spiderfier');
            $boundsJsVar = self::getJsVar($this->getId(), 'bounds');
            $markerJs = "
                $boundsJsVar = new google.maps.LatLngBounds();
                $spiderfierJsVar = new OverlappingMarkerSpiderfier({$mapJsVar}, { markersWontMove: true, markersWontHide: true });
                $markerJsVar = [];
            ";
            foreach ($this->markers as $index => $marker) {
                // wrap the infowindow in a declaration if needed
                $infoWindow = ArrayHelper::getValue($marker, 'infowindow');
                if ($infoWindow != null && !($infoWindow instanceof JsExpression)) {
                    $marker['infowindow'] = new JsExpression('new google.maps.InfoWindow(' . Json::encode($infoWindow) . ')');
                }
                $marker['map'] = new JsExpression($mapJsVar);

                $markerJs .= "{$markerJsVar}[$index] = new google.maps.Marker(" . Json::encode($marker) . ");\n";
                // ensure the map bounds cover the markers
                // add the marker to the map
                $markerJs .= "
                    $boundsJsVar.extend({$markerJsVar}[$index].getPosition());
                    $spiderfierJsVar.addMarker({$markerJsVar}[$index]);
                ";
            }
            // allow defining events (eg onclick)
            if ($this->markerEvents != null) {
                $markerJs .= "
                for (let i = 0; i < {$markerJsVar}.length; i++) {
                ";
                foreach ($this->markerEvents as $event => $js) {
                    $markerJs .= "google.maps.event.addListener({$markerJsVar}[i], '$event', $js);";
                }
                $markerJs .= "
                }";
            }
            // last step - fit the map around the markers we've dropped
            if ($this->autoContainMarkers) {
                $markerJs .= "{$mapJsVar}.fitBounds({$boundsJsVar});";
            }
            $this->getView()->registerJs($markerJs, View::POS_LOAD);
        }

        // define heatmap
        if ($this->heatmap != null) {
            foreach (ArrayHelper::getValue($this->heatmap, 'data') as $index => $loc) {
                $this->heatmap['data'][$index] = ['location' => new JsExpression('new new google.maps.LatLng(' . ArrayHelper::getValue($loc, 'lat') . ', ' . ArrayHelper::getValue($loc, 'lng') . ')')];
            }
            if (ArrayHelper::getValue($this->heatmap, 'map') == null) {
                $this->heatmap['map'] = $mapJsVar;
            }
            $heatmapJsVar = self::getJsHeatmapVar($this->getId());
            $this->getView()->registerJs("$heatmapJsVar = new google.maps.visualization.HeatmapLayer(" . Json::encode($this->heatmap) . ");", View::POS_LOAD);
        }
    }

    public static function getJsVar($id, $var) {
        return "{$id}_{$var}";
    }

    public static function getJsMapVar($id) {
        return self::getJsVar($id, 'map');
    }

    public static function getJsHeatmapVar($id) {
        return self::getJsVar($id, 'heatmap');
    }

    public static function getJsMarkersVar($id) {
        return self::getJsVar($id, 'markers');
    }

    /**
     * Get the URL of a MS Icon image
     * @param string $identifier colour of the body of the icon
     * @param bool $dot whether
     */
    public static function getMsMarkerImage($identifier, bool $dot) {
        if ($dot) {
            $dotString = self::MS_MARKER_DOT;
        } else {
            $dotString = self::MS_MARKER_NO_DOT;
        }
        return "https://maps.google.com/mapfiles/ms/icons/{$identifier}{$dotString}.png";
    }

    public function run() {
        return \yii\helpers\Html::tag(
            'div',
            '',
            $this->options
        );
    }
}
