# Source: bemap-services/bemap-geocoder.js

    /**
     * BeNomad BeMap JavaScript API - Geocoder
     */

    /**
     * @classdesc
     * Base class for geocoder.
     * @public
     * @constructor
     * @abstract
     * @param {bemap.Context} context BeMap-JS-API Context. Mandatory.
     * @param {object} options see below the available values.
     * @param {String} options.language Define the language that will be used to perform the address lookup.
     * @param {Int} options.maxResult The maximum number of items used to perform the research and returned items by the server. Default is 1.
     * @param {bemap.BoundingBox} options.boundingBox the bounding box that will limit the research to a specific area.
     */
    bemap.Geocoder = function(context, options) {
      bemap.Service.call(this, context, options);

      var opts = options || {};

      /**
       * @type {String}
       * @protected
       */
      this.language = opts.language ? opts.language : null;

      /**
       * @type {int}
       * @protected
       */
      this.maxResult = opts.maxResult ? opts.maxResult : 1;

      /**
       * @type {bemap.BoundingBox}
       * @protected
       */
      this.bbox = opts.boundingBox ? opts.boundingBox : null;

    };
    bemap.inherits(bemap.Geocoder, bemap.Service);

    /**
     * Get the language.
     * @return {String} language
     */
    bemap.Geocoder.prototype.getLanguage = function() {
      return this.language;
    };

    /**
     * Get the number of max results.
     * @return {Int} maxResult
     */
    bemap.Geocoder.prototype.getMaxResult = function() {
      return this.maxResult;
    };

    /**
     * Get the bounding box of the research.
     * @return {bemap.BoundingBox} bbox
     */
    bemap.Geocoder.prototype.getBoundingBox = function() {
      return this.bbox;
    };

    /**
     * Set the language of the research.
     * @param {String} language the new language to set.
     * @return {bemap.Geocoder} this.
     */
    bemap.Geocoder.prototype.setLanguage = function(language) {
      this.language = language;
      return this;
    };

    /**
     * Set the number of max results.
     * @param {Int} maxResult the new number of max results.
     * @return {bemap.Geocoder} this.
     */
    bemap.Geocoder.prototype.setMaxResult = function(maxResult) {
      this.maxResult = maxResult;
      return this;
    };

    /**
     * Set the bounding box of the research.
     * @param {bemap.BoundingBox} bbox the new bounding box to set.
     * @return {bemap.Geocoder} this.
     */
    bemap.Geocoder.prototype.setBoundingBox = function(bbox) {
      this.bbox = bbox;
      return this;
    };

    /**
     * Generate the BeMap request in URL encoded format.
     * @private
     * @param {object} options See below the available values.
     * @param {object} options.geoserver Geoserver name will be used for this computation.
     * @return {String} the request URL encoded.
     */
    bemap.Geocoder.prototype.buildRequest = function(options) {
      var opts = options || {};
      var data = '&geoserver=' + (opts.geoserver ? opts.geoserver : this.ctx.getGeoserver());

      var searchInfo = opts.searchInfo;

      if (!searchInfo) {
        return;
      }

      for (var prop in this) {
        if (searchInfo.hasOwnProperty(prop) && searchInfo[prop]) {
          if (prop === "bbox") {
            data += "&" + prop + "=" + searchInfo[prop].minLon + "," + searchInfo[prop].minLat + "," + searchInfo[prop].maxLon + "," + searchInfo[prop].maxLat;
          } else {
            data += "&" + prop + "=" + encodeURI(searchInfo[prop]);
          }
        } else if (this[prop] && this.hasOwnProperty(prop) && prop !== 'ctx') {
          if (prop === "bbox") {
            data += "&" + prop + "=" + this[prop].minLon + "," + this[prop].minLat + "," + this[prop].maxLon + "," + this[prop].maxLat;
          } else {
            data += "&" + prop + "=" + encodeURI(this[prop]);
          }
        }
      }

      for (prop in searchInfo) {
        if (searchInfo[prop] && searchInfo.hasOwnProperty(prop)) {
          if (!this.hasOwnProperty(prop)) {
            data += "&" + prop + "=" + encodeURI(searchInfo[prop]);
          }
        }
      }

      return data;
    };

    /**
     * Execute the geocoding research.
     * @public
     * @param {object} options See below the available values.
     * @param {bemap.RevGeoSearchInfo} options.searchInfo the information to to search.
     * @param {object} options.geoserver Geoserver name will be used for this computation.
     * @param {function} options.success the function to call in case of successed request.
     * @param {function} options.failed the function to call in case of failed request.
     * @return {bemap.Geocoder} this.
     */
    bemap.Geocoder.prototype.revGeocode = function(options) {
      //this.reset();

      var opts = options || {};

      if (!bemap.inheritsof(opts.searchInfo, bemap.RevGeoSearchInfo)) {
        console.error("SearchInfo is required!");
      }

      var i = 0;
      var first = true;
      var url = this.ctx.getBaseUrl() + 'bnd';
      var data = 'version=1.0.0&action=revgeocoding&format=json';

      if (this.ctx.isAuthInPost()) {
        data += '&' + this.ctx.getAuthUrlParams();
      } else {
        url += '?' + this.ctx.getAuthUrlParams();
      }

      data += this.buildRequest(opts);

      return this.execute(url, data, opts);
    };

    /**
     * Send a geocoding request to the bemap's server.
     * @public
     * @param {object} options See below the available values.
     * @param {bemap.GeoSearchInfo} options.searchInfo the information to to search.
     * @param {object} options.geoserver Geoserver name will be used for this computation.
     * @param {function} options.success the function to call in case of successed request.
     * @param {function} options.failed the function to call in case of failed request.
     * @return {bemap.Geocoder} this.
     */
    bemap.Geocoder.prototype.geocode = function(options) {
      //this.reset();

      var opts = options || {};

      if (!bemap.inheritsof(opts.searchInfo, bemap.GeoSearchInfo)) {
        console.error("SearchInfo is required!");
      }

      var i = 0;
      var first = true;
      var url = this.ctx.getBaseUrl() + 'bnd';
      var data = 'version=1.0.0&action=geocoding&format=json';

      if (this.ctx.isAuthInPost()) {
        data += '&' + this.ctx.getAuthUrlParams();
      } else {
        url += '?' + this.ctx.getAuthUrlParams();
      }

      data += this.buildRequest(opts);

      return this.execute(url, data, opts);
    };

    /**
     * Excute the request by calling the BeMap server and wait the answer.
     * @private
     * @param {object} options Request options.
     * @return {bemap.Routing} this
     */
    bemap.Geocoder.prototype.execute = function(url, data, options) {
      var opts = options || {};
      var _this = this;

      bemap.ajax(
        'POST',
        url,
        data,
        function(xhr, doc) {
          _this.responseParser(xhr, doc, opts);
        },
        function(xhr, doc) {
          _this.responseParser(xhr, doc, opts);
        }, {
          'requestFormat': 'urlencoded'
        }
      );

      return this;
    };

    /**
     * Convert the BeMap response to the BeMap JS API object.
     * @private
     **/
    bemap.Geocoder.prototype.responseParser = function(xhr, doc, options) {
      var opts = options || {};

      doc = JSON.parse(doc);
      if (this.checkErrorParser(xhr, doc, options)) {
        return;
      }

      var bnd = doc.BND;
      var elements = bnd.Elements.Element;
      var response = new bemap.GeocodingResponse();
      if (elements !== undefined) {
        for (var i = 0; i < elements.length; i++) {
          var element = elements[i];
          var geocodingItem = new bemap.GeocodingItem();

          for (var prop in element) {
            var elementProp = element[prop];
            if (!elementProp || !element.hasOwnProperty(prop)) {
              continue;
            }
            //add id to easly get difference between elements
            geocodingItem['index'] = i + 1;
            if (prop === 'PostalAddress') {
              var newPostalAddress = new bemap.PostalAddress({
                countryCode: elementProp.CountryCode ? elementProp.CountryCode : '',
                country: elementProp.Country ? elementProp.Country : '',
                state: elementProp.State ? elementProp.State : '',
                county: elementProp.County ? elementProp.County : '',
                city: elementProp.City ? elementProp.City : '',
                district: elementProp.District ? elementProp.District : '',
                postalCode: elementProp.PostalCode ? elementProp.PostalCode : '',
                street: elementProp.Street ? elementProp.Street : '',
                streetNumber: elementProp.StreetNumber ? elementProp.StreetNumber : ''
              });
              geocodingItem[prop].push(newPostalAddress);
            } else if (prop === 'Coordinate') {
              var newCoordinate = new bemap.Coordinate(elementProp.x, elementProp.y);
              geocodingItem[prop] = newCoordinate;
            } else if (prop === 'RoadFeature') {
              var newRoadFeature = new bemap.RoadFeature();
              for (var inf in elementProp) {
                newRoadFeature[inf] = elementProp[inf];
              }
              geocodingItem[prop].push(newRoadFeature);
            } else {
              geocodingItem[prop] = elementProp;
            }
          }

          response.geocodingItems.push(geocodingItem);
        }
      } else {
        console.error("The result of geocoding not found");
      }
      if (bnd.Extent) {
        response.extent = new bemap.BoundingBox(bnd.Extent.minX, bnd.Extent.minY, bnd.Extent.maxX, bnd.Extent.maxY);
      }
      response.action = bnd.action;
      if (opts.success) {
        opts.success(response, doc, this, xhr);
      }
    };

    /**
     * Create table in selected div with data from parsing. Event click send by listener
     * @public
     * @param {Object} response data for create table.
     * @param {object} options see below the available values.
     * @param {DIV} container element to create table inside.
     * @param {function} listener the function to call to get click data
     */
    bemap.Geocoder.prototype.createTable = function(options, listener) {
      //create list to store created markers
      if (!bemap.markerMapObject) {
        bemap.markerMapObject = [];
      };

      if (!options) {
        console.error("Options required");
      };

      if (!options.container) {
        console.error("Container required");
      };

      if (!options.response) {
        console.error("Response required");
      };

      var container = options.container;
      var doc = options.response;

      container.empty();

      var html = '<div><table class="table table-hover table-striped">';
      html += '<thead><th>City</th><th>Country</th><th>PostalCode</th><th>Place</th></thead><tbody>';

      for (var i = 0; i < doc.geocodingItems.length; i++) {

        var e = doc.geocodingItems[i];

        var p = e.PostalAddress[0];
        html += '<tr class="cursorPointer" onclick="myFunction(this)" data="' + i + '">';
        html += '<td>' + (p.city && p.city !== null ? p.city : '') + '</td>';
        html += '<td>' + (p.country && p.country !== null ? p.country : '') + '</td><td>' + (p.postalCode && p.postalCode !== null ? p.postalCode : '') + '</td>';
        html += '<td>' + (p.streetNumber && p.streetNumber !== null ? p.streetNumber : '') + ' ' + (p.street && p.street !== null ? p.street : '') + '</td></tr>';
      }

      html += '</tbody></table></div>';

      var theDiv = document.getElementById(container[0].id);
      theDiv.innerHTML += html;

      //var theTr = document.querySelector('#' + container[0].id + ' tbody tr');
      //used this solution because of deleting jquery
      myFunction = function(elem) {
        var index = elem.getAttribute('data');

        var e = doc.geocodingItems[index];

        if (listener) {
          listener(e);
        };
      };
    };

    /**
     * Parse data to create marker or markers on map
     * @public
     * @param {bemap.Map} map for creating new marker.
     * @param {Object} response data to parse in.
     * @param {object} options see below the available values.
     * @param {function} listener the function to call to get click data
     */
    bemap.Geocoder.prototype.showOnMap = function(options, listener) {

      var map = options.map ? options.map : bemap.map;
      var layer = options.layer ? options.layer : '';

      if (!options) {
        console.error("Options required");
      }

      if (!options.response) {
        console.error("Response required");
      }

      var doc = options.response;

      var icone = options.icone ? options.icone : {};

      this.cleanMarkers();
      //check if there is list of markers or only one marker
      //if list make a for loop else send directly to create marker
      if (doc.geocodingItems) {
        for (var i = 0; i < doc.geocodingItems.length; i++) {

          var e = doc.geocodingItems[i];

          this.createMarker(map, e, icone, layer, function(data) {
            if (listener) {
              listener(data);
            }
          })
        }
      } else {
        this.createMarker(map, doc, icone, layer, function(data) {
          if (listener) {
            listener(data);
          };
        });
      };
    };

    /**
     * Reset the geocoding marker object. Clear the previous result.
     * @public
     * @return {bemap.markerMapObject} this
     */
    bemap.Geocoder.prototype.cleanMarkers = function() {
      if (bemap.markerMapObject) {
        for (i = 0; i < bemap.markerMapObject.length; i++) {
          var marker = bemap.markerMapObject[i];
          marker.remove();
          marker = undefined;
        };
        bemap.markerMapObject = [];
      } else {
        bemap.markerMapObject = [];
      };
    };

    /**
     * Put markers on map
     * @private
     * @param {bemap.Map} map for creating new marker.
     * @param {Object} data data to pcreate marker.
     * @param {object} icone to personalize the marker.
     * @param {function} listener the function to call to get click data
     */
    bemap.Geocoder.prototype.createMarker = function(map, data, icone, layer, listener) {

      map = map ? map : bemap.map;
      icone = icone ? icone : {};

      var posx = data.Coordinate.lon;
      var posy = data.Coordinate.lat;
      var c = new bemap.Coordinate(posx, posy);
      var icon = new bemap.Icon({
        src: icone.src ? icone.src : console.error("Check your icon adress REQUIRED ICON"),
        anchorX: icone.anchorX ? icone.anchorX : 0.25,
        anchorY: icone.anchorY ? icone.anchorY : 1,
        height: icone.height ? icone.height : '',
        width: icone.width ? icone.width : '',
        anchorXUnits: icone.anchorXUnits ? icone.anchorXUnits : 'fraction',
        anchorYUnits: icone.anchorYUnits ? icone.anchorYUnits : 'fraction',
        scale: icone.scale ? icone.scale : 1
      });
      //before creating new markers check if there was already created
      //this condition was made because of two posibilities of creation the markers
      //first by list second one by one
      if (bemap.markerMapObject) {
        for (var i = 0; i < bemap.markerMapObject.length; i++) {
          var mark = bemap.markerMapObject[i];
          if (mark.id == data.index) {
            map.move(posx, posy, 16);
            return
          };
        };
      };

      var marker = new bemap.Marker(
        c, {
          properties: data,
          icon: icon,
          id: data.index
        }
      );

      if (layer) {
        map.addMarker(marker, {
          layer: layer
        });
      } else {
        map.addMarker(marker);
      };
      //save list of markers to clean it later or to prevent dubbles
      bemap.markerMapObject.push(marker);

      marker.on(bemap.Map.EventType.CLICK, function(mapEvent) {
        if (listener) {
          listener(mapEvent);
        };
      });
    };