/*	Copyright (c) 2017 Jean-Marc VIGLINO,
	released under the CeCILL-B license (French BSD license)
	(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
import {transform as ol_proj_transform} from 'ol/proj.js'
import {transformExtent as ol_proj_transformExtent} from 'ol/proj.js'
import ol_control_Search from './Search.js'
import ol_control_SearchJSON from './SearchJSON.js'

/**
 * Search places using the French National Base Address (BAN) API.
 *
 * @constructor
 * @extends {ol.control.SearchJSON}
 * @fires select
 * @param {any} options extend ol.control.SearchJSON options
 *	@param {string} options.className control class name
 *	@param {string | undefined} [options.apiKey] the service api key.
 *	@param {string | undefined} [options.version] API version 1 or 2 or geoplateforme (latest), default latest
 *	@param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd")
 *	@param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport.
 *	@param {string | undefined} options.label Text label to use for the search button, default "search"
 *	@param {boolean | undefined} options.reverse enable reverse geocoding, default false
 *	@param {string | undefined} options.placeholder placeholder, default "Search..."
 *	@param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500.
 *	@param {integer | undefined} options.minLength minimum length to start searching, default 3
 *	@param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10
 *	@param {StreetAddress|PositionOfInterest|CadastralParcel|Commune} [options.type] type of search. Using Commune will return the INSEE code, default StreetAddress,PositionOfInterest
 *	@param {string} [options.terr] territory METROPOLE|DOMTOM|dep code
 *  @param {boolean} [options.position] Search, with priority to geo position (map center), default false
 *	@param {ol.extent} [options.bbox] if set search inside the bbox (in map projection)
 *	@param {boolean} [options.useExtent] returns candidates inside the current map extent, default false
 * @see {@link https://geoservices.ign.fr/documentation/geoservices/geocodage.html}
 * @see {@link https://geoservices.ign.fr/documentation/services/services-deprecies/itineraires-deprecies/autocompletion-rest}
 * @see {@link https://geoservices.ign.fr/documentation/services/api-et-services-ogc/geocodage-beta-20/documentation-technique-de-lapi}
 */
var ol_control_SearchGeoportail = class olcontrolSearchGeoportail extends ol_control_SearchJSON {
  constructor(options) {
    options = options || {};
    options.className = options.className || 'IGNF';
    options.typing = options.typing || 500;
    if (options.version == 1) {
      options.url = 'https://wxs.ign.fr/' + (options.apiKey || 'essentiels') + '/ols/apis/completion';
      options.copy = '<a href="https://www.geoportail.gouv.fr/" target="new">&copy; IGN-Géoportail</a>';
    } else if (options.version == 2) {
      options.url = 'https://wxs.ign.fr/' + (options.apiKey || 'essentiels') + '/geoportail/geocodage/rest/0.1/completion';
      options.copy = '<a href="https://www.geoportail.gouv.fr/" target="new">&copy; IGN-Géoportail</a>';
    } else {
      options.url = 'https://data.geopf.fr/geocodage/completion';
      options.copy = '<a href="https://geoservices.ign.fr/" target="new">&copy; IGN-Géoplateforme</a>';
    }
    super(options);
    this.set('position', options.position);
    this.set('useExtent', options.useExtent);
    this.set('bbox', options.bbox)
    this.set('type', options.type || 'StreetAddress,PositionOfInterest');
    this.set('terr', options.terr);
    this.set('timeout', options.timeout || 2000);
    // Authentication
    // this._auth = options.authentication;
  }
  /** Reverse geocode
   * @param {ol.coordinate} coord
   * @param {function|*} options callback function called when revers located or options passed to the select event
   * @api
   */
  reverseGeocode(coord, options) {
    var lonlat = ol_proj_transform(coord, this.getMap().getView().getProjection(), 'EPSG:4326');
    this._handleSelect({
      x: lonlat[0],
      y: lonlat[1],
      fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6)
    }, true, options);

    // Search type
    var type = this.get('type') === 'Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress';
    if (/,/.test(type)) type = 'StreetAddress';

    // Search url
    var url = this.get('url').replace('ols/apis/completion', 'geoportail/ols').replace('completion', 'reverse');
    if (/ols/.test(url)) {
      // request
      var request = '<?xml version="1.0" encoding="UTF-8"?>'
        + '<XLS xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.opengis.net/xls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.2/olsAll.xsd">'
        + ' <Request requestID="1" version="1.2" methodName="ReverseGeocodeRequest" maximumResponses="1" >'
        + '  <ReverseGeocodeRequest>'
        + '   <ReverseGeocodePreference>' + type + '</ReverseGeocodePreference>'
        + '   <Position>'
        + '    <gml:Point><gml:pos>' + lonlat[1] + ' ' + lonlat[0] + '</gml:pos></gml:Point>'
        + '   </Position>'
        + '  </ReverseGeocodeRequest>'
        + ' </Request>'
        + '</XLS>';

      this.ajax(url,
        { xls: request },
        function (xml) {
          var f = {};
          if (!xml) {
            f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) };
          } else {
            xml = xml.replace(/\n|\r/g, '');
            var p = (xml.replace(/.*<gml:pos>(.*)<\/gml:pos>.*/, "$1")).split(' ');
            if (!Number(p[1]) && !Number(p[0])) {
              f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) };
            } else {
              f.x = lonlat[0];
              f.y = lonlat[1];
              f.city = (xml.replace(/.*<Place type="Municipality">([^<]*)<\/Place>.*/, "$1"));
              f.insee = (xml.replace(/.*<Place type="INSEE">([^<]*)<\/Place>.*/, "$1"));
              f.zipcode = (xml.replace(/.*<PostalCode>([^<]*)<\/PostalCode>.*/, "$1"));
              if (/<Street>/.test(xml)) {
                f.kind = '';
                f.country = 'StreetAddress';
                f.street = (xml.replace(/.*<Street>([^<]*)<\/Street>.*/, "$1"));
                var number = (xml.replace(/.*<Building number="([^"]*).*/, "$1"));
                f.fulltext = number + ' ' + f.street + ', ' + f.zipcode + ' ' + f.city;
              } else {
                f.kind = (xml.replace(/.*<Place type="Nature">([^<]*)<\/Place>.*/, "$1"));
                f.country = 'PositionOfInterest';
                f.street = '';
                f.fulltext = f.zipcode + ' ' + f.city;
              }
            }
          }
          if (typeof (options) === 'function') {
            options.call(this, [f]);
          } else {
            this.getHistory().shift();
            this._handleSelect(f, true, options);
            // this.setInput('', true);
            // this.drawList_();
          }
        }.bind(this), {
        timeout: this.get('timeout'),
        dataType: 'XML'
      });
    } else {
      this.ajax(url + '?lon='+lonlat[0] + '&lat=' + lonlat[1], 
        {}, 
        function(resp) {
          var f;
          try {
            resp = JSON.parse(resp).features[0];
            f = resp.properties;
            // lonlat
            f.x = resp.geometry.coordinates[0];
            f.y = resp.geometry.coordinates[1];
            f.click = lonlat;
            // Fulltext
            if (f.name) {
              f.fulltext = f.name + ', ' + f.postcode + ' ' + f.city;
            } else {
              f.fulltext = f.postcode + ' ' + f.city;
            }
          } catch(e) {
            f = { 
              x: lonlat[0], 
              y: lonlat[1], 
              lonlat: lonlat,
              fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) 
            };
          }
          if (typeof (options) === 'function') {
            options.call(this, [f]);
          } else {
            this.getHistory().shift();
            this._handleSelect(f, true, options);
            // this.setInput('', true);
            // this.drawList_();
          }
        }.bind(this), {
          timeout: this.get('timeout'),
          dataType: 'XML'
        }
      );
    }
  }
  /** Returns the text to be displayed in the menu
   *	@param {ol.Feature} f the feature
   *	@return {string} the text to be displayed in the index
   *	@api
   */
  getTitle(f) {
    return (f.fulltext);
  }
  /**
   * @param {string} s the search string
   * @return {Object} request data (as key:value)
   * @api
   */
  requestData(s) {
    var rdata = {
      text: s,
      type: this.get('type') === 'Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress,PositionOfInterest',
      terr: this.get('terr') || undefined,
      maximumResponses: this.get('maxItems')
    };
    if (this.get('type') === 'Commune') rdata.poiType = 'commune';
    if (this.get('position')) {
      var center = this.getMap().getView().getCenter()
      rdata.lonlat = ol_proj_transform(center, this.getMap().getView().getProjection(), 'EPSG:4326').join(',');
    }
    if (this.get('bbox')) {
      rdata.bbox = ol_proj_transformExtent(this.get('bbox'), this.getMap().getView().getProjection(), 'EPSG:4326').join(',')
    } else if (this.get('useExtent')) {
      var bbox = this.getMap().getView().calculateExtent()
      rdata.bbox = ol_proj_transformExtent(bbox, this.getMap().getView().getProjection(), 'EPSG:4326').join(',')
    }
    return rdata;
  }
  /**
   * Handle server response to pass the features array to the display list
   * @param {any} response server response
   * @return {Array<any>} an array of feature
   * @api
   */
  handleResponse(response) {
    return response.results;
  }
  /** A ligne has been clicked in the menu > dispatch event
   * @param {any} f the feature, as passed in the autocomplete
   * @param {boolean} reverse true if reverse geocode
   * @param {ol.coordinate} coord
   * @param {*} options options passed to the event
   *	@api
   */
  select(f, reverse, coord, options) {
    if (f.x || f.y) {
      var c = [Number(f.x), Number(f.y)];
      // Add coordinate to the event
      try {
        c = ol_proj_transform(c, 'EPSG:4326', this.getMap().getView().getProjection());
      } catch (e) { /* ok */ }
      // Get insee commune ?
      if (this.get('type') === 'Commune') {
        this.searchCommune(f, function () {
          ol_control_Search.prototype.select.call(this, f, reverse, c, options);
        }.bind(this));
      } else {
        super.select(f, reverse, c, options);
      }
    } else {
      this.searchCommune(f);
    }
  }
  /** Search if no position and get the INSEE code
   * @param {string} s le nom de la commune
   */
  searchCommune(f, cback) {
    // Search url
    var url = this.get('url').replace('ols/apis/completion', 'geoportail/ols').replace('completion', 'reverse');
    if (/ols/.test(url)) {
      var request = '<?xml version="1.0" encoding="UTF-8"?>'
        + '<XLS xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.opengis.net/xls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.2/olsAll.xsd">'
        + '<RequestHeader/>'
        + '<Request requestID="1" version="1.2" methodName="LocationUtilityService">'
        + '<GeocodeRequest returnFreeForm="false">'
        + '<Address countryCode="PositionOfInterest">'
        + '<freeFormAddress>' + f.zipcode + ' ' + f.city + '+</freeFormAddress>'
        + '</Address>'
        + '</GeocodeRequest>'
        + '</Request>'
        + '</XLS>';

      // Search 
      this.ajax(this.get('url').replace('ols/apis/completion', 'geoportail/ols'),
        { 'xls': request },
        function (xml) {
          if (xml) {
            // XML to JSON
            var parser = new DOMParser();
            var xmlDoc = parser.parseFromString(xml, "text/xml");
            var com = xmlDoc.getElementsByTagName('GeocodedAddress')[0];
            var coord = com.getElementsByTagName('gml:Point')[0].textContent.trim().split(' ');
            f.x = Number(coord[1]);
            f.y = Number(coord[0]);
            var place = com.getElementsByTagName('Place');
            for (var i = 0; i < place.length; i++) {
              switch (place[i].attributes.type.value) {
                case 'Nature':
                  f.kind = place[i].textContent;
                  break;
                case 'INSEE':
                  f.insee = place[i].textContent;
                  break;
              }
            }
            if (f.x || f.y) {
              if (cback)
                cback.call(this, [f]);
              else
                this._handleSelect(f);
            }
          }
        }.bind(this),
        { dataType: 'XML' }
      );
    } else {
      this.ajax(url + '?lon=' + f.x + '&lat=' + f.y + '&index=parcel&limit=1', 
        {},
        function (resp) {
          try {
            var r = JSON.parse(resp).features[0];
            f.insee = r.properties.departmentcode + r.properties.municipalitycode
            f.districtcode = r.properties.districtcode
            // f.insee = r.properties.citycode
            if (cback) {
              cback.call(this, [f]);
            } else {
              this._handleSelect(f);
            }
          } catch(e) { /* ok */ }
        }.bind(this), {
          timeout: this.get('timeout'),
          dataType: 'XML'
        }
      )
    } 

  }
}

export default ol_control_SearchGeoportail
