# Source: src/js/views/maps/MapView.js

    'use strict';

    define(
      [
        'jquery',
        'underscore',
        'backbone',
        'models/maps/Map',
        'text!templates/maps/map.html',
        // SubViews
        'views/maps/CesiumWidgetView',
        'views/maps/ToolbarView',
        'views/maps/ScaleBarView',
        'views/maps/FeatureInfoView',
        'views/maps/LayerDetailsView',
        // CSS
        'text!' + MetacatUI.root + '/css/map-view.css',
      ],
      function (
        $,
        _,
        Backbone,
        Map,
        Template,
        // SubViews
        CesiumWidgetView,
        ToolbarView,
        ScaleBarView,
        FeatureInfoView,
        LayerDetailsView,
        // CSS
        MapCSS
      ) {

        /**
        * @class MapView
        * @classdesc An interactive 2D or 3D map that allows visualization of geo-spatial
        * data.
        * @classcategory Views/Maps
        * @name MapView
        * @extends Backbone.View
        * @screenshot views/maps/MapView.png
        * @since 2.18.0
        * @constructs
        */
        var MapView = Backbone.View.extend(
          /** @lends MapView.prototype */{

            /**
            * The type of View this is
            * @type {string}
            */
            type: 'MapView',

            /**
            * The HTML classes to use for this view's element
            * @type {string}
            */
            className: 'map-view',

            /**
            * The model that this view uses
            * @type {Map}
            */
            model: null,

            /**
             * The primary HTML template for this view
             * @type {Underscore.template}
             */
            template: _.template(Template),

            /**
             * Classes that will be used to select specific elements from the template.
             * @name MapView#classes
             * @type {Object}
             * @property {string} mapWidgetContainer The element that will hold the map widget
             * (i.e. CesiumWidgetView)
             * @property {string} scaleBarContainer The container for the ScaleBarView
             * @property {string} featureInfoContainer The container for the box that will
             * show details about a selected feature
             * @property {string} toolbarContainer The container for the toolbar UI
             * @property {string} layerDetailsContainer The container for the element that
             * will show details about a specific layer
             */
            classes: {
              mapWidgetContainer: 'map-view__map-widget-container',
              scaleBarContainer: 'map-view__scale-bar-container',
              featureInfoContainer: 'map-view__feature-info-container',
              toolbarContainer: 'map-view__toolbar-container',
              layerDetailsContainer: 'map-view__layer-details-container'
            },

            /**
            * The events this view will listen to and the associated function to call.
            * @type {Object}
            */
            events: {
              // 'event selector': 'function',
            },

            /**
            * Executed when a new MapView is created
            * @param {Object} [options] - A literal object with options to pass to the view.
            */
            initialize: function (options) {

              try {
                // Add the CSS required for this view and its sub-views.
                MetacatUI.appModel.addCSS(MapCSS, 'mapView');

                // Get all the options and apply them to this view
                if (typeof options == 'object') {
                  for (const [key, value] of Object.entries(options)) {
                    this[key] = value;
                  }
                }

                if(!this.model) {
                  this.model = new Map();
                }

              } catch (e) {
                console.log('A MapView failed to initialize. Error message: ' + e);
              }

            },

            /**
            * Renders this view
            * @return {MapView} Returns the rendered view element
            */
            render: function () {

              try {

                // Save a reference to this view
                var view = this;

                // TODO: Add a nice loading animation?

                // Insert the template into the view
                this.$el.html(this.template());

                // Ensure the view's main element has the given class name
                this.el.classList.add(this.className);

                // Select the elements that will be updatable
                this.subElements = {};
                for (const [element, className] of Object.entries(view.classes)) {
                  view.subElements[element] = document.querySelector('.' + className)
                }

                // Render the (Cesium) map
                this.renderMapWidget();

                // Optionally add the toolbar, layer details, scale bar, and feature info box.
                if (this.model.get('showToolbar')) {
                  this.renderToolbar();
                  this.renderLayerDetails();
                }
                if (this.model.get('showScaleBar')) {
                  this.renderScaleBar();
                }
                if (this.model.get('showFeatureInfo')) {
                  this.renderFeatureInfo();
                }

                // Return this MapView
                return this

              }
              catch (error) {
                console.log(
                  'There was an error rendering a MapView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Renders the view that shows the map/globe and all of the geo-spatial data.
             * Currently, this uses the CesiumWidgetView, but this function could be modified
             * to use an alternative map widget in the future.
             * @returns {CesiumWidgetView} Returns the rendered view
             */
            renderMapWidget: function () {
              try {
                this.mapWidget = new CesiumWidgetView({
                  el: this.subElements.mapWidgetContainer,
                  model: this.model
                })
                this.mapWidget.render()
                return this.mapWidget
              }
              catch (error) {
                console.log(
                  'There was an error rendering the map widget in a MapView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Renders the toolbar element that contains sections for viewing and editing the
             * layer list.
             * @returns {ToolbarView} Returns the rendered view
             */
            renderToolbar: function () {
              try {
                this.toolbar = new ToolbarView({
                  el: this.subElements.toolbarContainer,
                  model: this.model
                })
                this.toolbar.render()
                return this.toolbar
              }
              catch (error) {
                console.log(
                  'There was an error rendering a toolbarView in a MapView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Renders the info box that is displayed when a user clicks on a feature on the
             * map. If there are multiple features selected, this will show information for
             * the first one only.
             * @returns {FeatureInfoView}  Returns the rendered view
             */
            renderFeatureInfo: function () {
              try {
                this.featureInfo = new FeatureInfoView({
                  el: this.subElements.featureInfoContainer,
                  model: this.model.get('selectedFeatures').at(0)
                })
                this.featureInfo.render()

                // When the selectedFeatures collection changes, update the feature info view
                function setSelectFeaturesListeners() {
                  this.stopListening(this.model.get('selectedFeatures'), 'update')
                  this.listenTo(this.model.get('selectedFeatures'), 'update', function () {
                    this.featureInfo.changeModel(this.model.get('selectedFeatures').at(-1))
                  })
                }
                setSelectFeaturesListeners.call(this)

                // If the Feature model is ever completely replaced for any reason, make the
                // the Feature Info view gets updated.
                this.stopListening(this.model, 'change:selectedFeatures')
                this.listenTo(this.model, 'change:selectedFeatures', function (mapModel, featuresCollection) {
                  this.featureInfo.changeModel(featuresCollection.at(-1))
                  setSelectFeaturesListeners.call(this)
                })
                return this.featureInfo
              }
              catch (error) {
                console.log(
                  'There was an error rendering a FeatureInfoView in a MapView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Renders the layer details view that is displayed when a user clicks on a layer
             * in the toolbar.
             * @returns {LayerDetailsView} Returns the rendered view
             */
            renderLayerDetails: function () {
              try {
                this.layerDetails = new LayerDetailsView({
                  el: this.subElements.layerDetailsContainer
                })
                this.layerDetails.render()

                // When a layer is selected, show the layer details panel. When a layer is
                // de-selected, close it. The Layer model's 'selected' attribute gets updated
                // from the Layer Item View, and also from the Layers collection.
                this.stopListening(this.model.get('layers'))
                this.listenTo(this.model.get('layers'), 'change:selected',
                  function (layerModel, selected) {
                    if (selected === false) {
                      this.layerDetails.updateModel(null)
                      this.layerDetails.close()
                    } else {
                      this.layerDetails.updateModel(layerModel)
                      this.layerDetails.open()
                    }
                  }
                )

                return this.layerDetails
              }
              catch (error) {
                console.log(
                  'There was an error rendering a LayerDetailsView in a MapView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Renders the scale bar view that shows the current position of the mouse on the
             * map.
             * @returns {ScaleBarView} Returns the rendered view
             */
            renderScaleBar: function () {
              try {
                this.scaleBar = new ScaleBarView({
                  el: this.subElements.scaleBarContainer
                })
                this.scaleBar.render()

                this.stopListening(this.model, 'change:currentPosition')
                this.listenTo(this.model, 'change:currentPosition', function (model, position) {
                  this.scaleBar.updateCoordinates(position.latitude, position.longitude)
                })

                this.stopListening(this.model, 'change:currentScale')
                this.listenTo(this.model, 'change:currentScale', function (model, scale) {
                  this.scaleBar.updateScale(scale.pixels, scale.meters)
                })

                return this.scaleBar
              }
              catch (error) {
                console.log(
                  'There was an error rendering a ScaleBarView in a MapView' +
                  '. Error details: ' + error
                );
              }
            },

          }
        );

        return MapView;

      }
    );