text: 'use strict';
  
  var polyline = require('@mapbox/polyline');
  var v = require('./service-helpers/validator');
  var createServiceFactory = require('./service-helpers/create-service-factory');
  var pick = require('./service-helpers/pick');
  
  /**
   * Static Images API service.
   *
   * Learn more about this service and its responses in
   * [the HTTP service documentation](https://docs.mapbox.com/api/maps/#static-images).
   */
  var Static = {};
  
  /**
   * Get a static map image.
   *
   * **If you just want the URL for the static map image, create a request
   * and get it's URL with `MapiRequest#url`.** This is what prior versions of the
   * SDK returned.
   *
   * @param {Object} config
   * @param {string} config.ownerId - The owner of the map style.
   * @param {string} config.styleId - The map's style ID.
   * @param {number} config.width - Width of the image in pixels, between 1 and 1280.
   * @param {number} config.height - Height of the image in pixels, between 1 and 1280.
   * @param {'auto'|Object} config.position - If `"auto"`, the viewport will fit the
   *   bounds of the overlay(s). Otherwise, the maps' position is described by an object
   *   with the following properties:
   *   `coordinates` (required): [`coordinates`](#coordinates) for the center of image.
   *   `zoom` (required): Between 0 and 20.
   *   `bearing` (optional): Between 0 and 360.
   *   `pitch` (optional): Between 0 and 60.
   *
   * @param {Array<Overlay>} [config.overlays] - Overlays should be in z-index
   *   order: the first in the array will be on the bottom; the last will be on
   *   the top. Overlays are objects that match one of the following types:
   *   [`SimpleMarkerOverlay`](#simplemarkeroverlay),
   *   [`CustomMarkerOverlay`](#custommarkeroverlay),
   *   [`PathOverlay`](#pathoverlay),
   *   [`GeoJsonOverlay`](#geojsonoverlay)
   *
   * @param {boolean} [config.highRes=false]
   * @param {string} [config.before_layer] - The ID of the style layer
   *   that overlays should be inserted *before*.
   * @param {Object} [config.addlayer] - Adds a Mapbox style layer to the map's style at render time. Can be combined with before_layer.
   * @param {Array} [config.setfilter] - Applies a filter to an existing layer in a style using Mapbox's expression syntax. Must be used with layer_id.
   * @param {string} [config.layer_id] - Denotes the layer in the style that the filter specified in setfilter is applied to.
   * @param {boolean} [config.attribution=true] - Whether there is attribution
   *   on the map image.
   * @param {boolean} [config.logo=true] - Whether there is a Mapbox logo
   *   on the map image.
   * @return {MapiRequest}
   *
   * @example
   * staticClient.getStaticImage({
   *   ownerId: 'mapbox',
   *   styleId: 'streets-v11',
   *   width: 200,
   *   height: 300,
   *   position: {
   *     coordinates: [12, 13],
   *     zoom: 4
   *   }
   * })
   *   .send()
   *   .then(response => {
   *     const image = response.body;
   *   });
   *
   * @example
   * staticClient.getStaticImage({
   *   ownerId: 'mapbox',
   *   styleId: 'streets-v11',
   *   width: 200,
   *   height: 300,
   *   position: {
   *     coordinates: [12, 13],
   *     zoom: 3
   *   },
   *   overlays: [
   *     // Simple markers.
   *     {
   *       marker: {
   *         coordinates: [12.2, 12.8]
   *       }
   *     },
   *     {
   *       marker: {
   *         size: 'large',
   *         coordinates: [14, 13.2],
   *         label: 'm',
   *         color: '#000'
   *       }
   *     },
   *     {
   *       marker: {
   *         coordinates: [15, 15.2],
   *         label: 'airport',
   *         color: '#ff0000'
   *       }
   *     },
   *     // Custom marker
   *     {
   *       marker: {
   *         coordinates: [10, 11],
   *         url:  'https://upload.wikimedia.org/wikipedia/commons/6/6f/0xff_timetracker.png'
   *       }
   *     }
   *   ]
   * })
   *   .send()
   *   .then(response => {
   *     const image = response.body;
   *   });
   *
   * @example
   * // To get the URL instead of the image, create a request
   * // and get its URL without sending it.
   * const request = staticClient
   *   .getStaticImage({
   *     ownerId: 'mapbox',
   *     styleId: 'streets-v11',
   *     width: 200,
   *     height: 300,
   *     position: {
   *       coordinates: [12, 13],
   *       zoom: 4
   *     }
   *   });
   * const staticImageUrl = request.url();
   * // Now you can open staticImageUrl in a browser.
   *
   * @example
   * // Filter all buildings that have a height value that is less than 300 meters
   * const request = staticClient
   *   .getStaticImage({
   *     ownerId: 'mapbox',
   *     styleId: 'streets-v11',
   *     width: 200,
   *     height: 300,
   *     position: {
   *       coordinates: [12, 13],
   *       zoom: 4
   *     },
   *     setfilter: [">","height",300],
   *     layer_id: 'building',
   *   });
   * const staticImageUrl = request.url();
   * // Now you can open staticImageUrl in a browser.
   *
   * @example
   * // Paint all the state and province level boundaries associated with the US worldview with a dashed line and insert it below the road-label layer
   * const request = staticClient
   *   .getStaticImage({
   *     ownerId: 'mapbox',
   *     styleId: 'streets-v11',
   *     width: 200,
   *     height: 300,
   *     position: {
   *       coordinates: [12, 13],
   *       zoom: 4
   *     },
   *     addlayer: {"id":"better-boundary","type":"line","source":"composite","source-layer":"admin","filter":["all",["==",["get","admin_level"],1],["==",["get","maritime"],"false"],["match",["get","worldview"],["all","US"],true,false]],"layout":{"line-join":"bevel"},"paint":{"line-color":"%236898B3","line-width":1.5,"line-dasharray":[1.5,1]}},
   *    before_layer: 'road-label',
   *   });
   * const staticImageUrl = request.url();
   * // Now you can open staticImageUrl in a browser.
  
   */
  Static.getStaticImage = function(config) {
    v.assertShape({
      ownerId: v.required(v.string),
      styleId: v.required(v.string),
      width: v.required(v.range([1, 1280])),
      height: v.required(v.range([1, 1280])),
      position: v.required(
        v.oneOfType(
          v.oneOf('auto'),
          v.strictShape({
            coordinates: v.required(v.coordinates),
            zoom: v.required(v.range([0, 20])),
            bearing: v.range([0, 360]),
            pitch: v.range([0, 60])
          })
        )
      ),
      overlays: v.arrayOf(v.plainObject),
      highRes: v.boolean,
      before_layer: v.string,
      addlayer: v.plainObject,
      setfilter: v.plainArray,
      layer_id: v.string,
      attribution: v.boolean,
      logo: v.boolean
    })(config);
  
    var encodedOverlay = (config.overlays || [])
      .map(function(overlayItem) {
        if (overlayItem.marker) {
          return encodeMarkerOverlay(overlayItem.marker);
        }
        if (overlayItem.path) {
          return encodePathOverlay(overlayItem.path);
        }
        return encodeGeoJsonOverlay(overlayItem.geoJson);
      })
      .join(',');
  
    var encodedPosition = encodePosition(config.position);
    var encodedDimensions = config.width + 'x' + config.height;
    if (config.highRes) {
      encodedDimensions += '@2x';
    }
  
    var preEncodedUrlParts = [encodedOverlay, encodedPosition, encodedDimensions]
      .filter(Boolean)
      .join('/');
  
    var query = {};
    if (config.attribution !== undefined) {
      query.attribution = String(config.attribution);
    }
    if (config.logo !== undefined) {
      query.logo = String(config.logo);
    }
    if (config.before_layer !== undefined) {
      query.before_layer = config.before_layer;
    }
    if (config.addlayer !== undefined) {
      query.addlayer = JSON.stringify(config.addlayer); // stringify to retain object shape
    }
    if (config.setfilter !== undefined) {
      query.setfilter = JSON.stringify(config.setfilter); // stringify to retain array shape
    }
    if (config.layer_id !== undefined) {
      query.layer_id = config.layer_id;
    }
  
    if (config.setfilter !== undefined && config.layer_id === undefined) {
      throw new Error('Must include layer_id in setfilter request');
    }
  
    if (
      (config.setfilter !== undefined || config.addlayer !== undefined) &&
      config.position === 'auto' &&
      config.overlays === undefined
    ) {
      throw new Error(
        'Auto extent cannot be used with style parameters and no overlay'
      );
    }
  
    if (config.addlayer !== undefined && config.setfilter !== undefined) {
      throw new Error(
        'addlayer and setfilter cannot be used in the same request'
      );
    }
  
    return this.client.createRequest({
      method: 'GET',
      path: '/styles/v1/:ownerId/:styleId/static/' + preEncodedUrlParts,
      params: pick(config, ['ownerId', 'styleId']),
      query: query
    });
  };
  
  function encodePosition(position) {
    if (position === 'auto') return 'auto';
  
    return position.coordinates
      .concat([position.zoom, position.bearing, position.pitch])
      .filter(Boolean)
      .join(',');
  }
  
  function encodeMarkerOverlay(o) {
    if (o.url) {
      return encodeCustomMarkerOverlay(o);
    }
    return encodeSimpleMarkerOverlay(o);
  }
  
  /**
   * A simple marker overlay.
   * @typedef {Object} SimpleMarkerOverlay
   * @property {Object} marker
   * @property {[number, number]} marker.coordinates - `[longitude, latitude]`
   * @property {'large'|'small'} [marker.size='small']
   * @property {string} [marker.label] - Marker symbol. Options are an alphanumeric label `a`
   *   through `z`, `0` through `99`, or a valid [Maki](https://www.mapbox.com/maki/)
   *   icon. If a letter is requested, it will be rendered in uppercase only.
   * @property {string} [marker.color] - A 3- or 6-digit hexadecimal color code.
   */
  
  function encodeSimpleMarkerOverlay(o) {
    v.assertShape({
      coordinates: v.required(v.coordinates),
      size: v.oneOf('large', 'small'),
      label: v.string,
      color: v.string
    })(o);
  
    var result = o.size === 'large' ? 'pin-l' : 'pin-s';
    if (o.label) {
      result += '-' + String(o.label).toLowerCase();
    }
    if (o.color) {
      result += '+' + sanitizeHexColor(o.color);
    }
    result += '(' + o.coordinates.join(',') + ')';
    return result;
  }
  
  /**
   * A marker overlay with a custom image.
   * @typedef {Object} CustomMarkerOverlay
   * @property {Object} marker
   * @property {[number, number]} marker.coordinates - `[longitude, latitude]`
   * @property {string} marker.url
   */
  
  function encodeCustomMarkerOverlay(o) {
    v.assertShape({
      coordinates: v.required(v.coordinates),
      url: v.required(v.string)
    })(o);
  
    var result = 'url-' + encodeURIComponent(o.url);
    result += '(' + o.coordinates.join(',') + ')';
    return result;
  }
  
  /**
   * A stylable line.
   * @typedef {Object} PathOverlay
   * @property {Object} path
   * @property {Array<Coordinates>} path.coordinates - An array of coordinates
   *   describing the path.
   * @property {number} [path.strokeWidth]
   * @property {string} [path.strokeColor]
   * @property {number} [path.strokeOpacity] - Must be paired with strokeColor.
   * @property {string} [path.fillColor] - Must be paired with strokeColor.
   * @property {number} [path.fillOpacity] - Must be paired with fillColor.
   */
  
  function encodePathOverlay(o) {
    v.assertShape({
      coordinates: v.required(v.arrayOf(v.coordinates)),
      strokeWidth: v.number,
      strokeColor: v.string,
      strokeOpacity: v.number,
      fillColor: v.string,
      fillOpacity: v.number
    })(o);
  
    if (o.strokeOpacity !== undefined && o.strokeColor === undefined) {
      throw new Error('strokeOpacity requires strokeColor');
    }
    if (o.fillColor !== undefined && o.strokeColor === undefined) {
      throw new Error('fillColor requires strokeColor');
    }
    if (o.fillOpacity !== undefined && o.fillColor === undefined) {
      throw new Error('fillOpacity requires fillColor');
    }
  
    var result = 'path';
    if (o.strokeWidth) {
      result += '-' + o.strokeWidth;
    }
    if (o.strokeColor) {
      result += '+' + sanitizeHexColor(o.strokeColor);
    }
    if (o.strokeOpacity) {
      result += '-' + o.strokeOpacity;
    }
    if (o.fillColor) {
      result += '+' + sanitizeHexColor(o.fillColor);
    }
    if (o.fillOpacity) {
      result += '-' + o.fillOpacity;
    }
    // polyline expects each coordinate to be in reversed order: [lat, lng]
    var reversedCoordinates = o.coordinates.map(function(c) {
      return c.reverse();
    });
    var encodedPolyline = polyline.encode(reversedCoordinates);
    result += '(' + encodeURIComponent(encodedPolyline) + ')';
    return result;
  }
  
  /**
   * GeoJSON to overlay the map.
   * @typedef {Object} GeoJsonOverlay
   * @property {Object} geoJson - Valid GeoJSON.
   */
  
  function encodeGeoJsonOverlay(o) {
    v.assert(v.required(v.plainObject))(o);
  
    return 'geojson(' + encodeURIComponent(JSON.stringify(o)) + ')';
  }
  
  function sanitizeHexColor(color) {
    return color.replace(/^#/, '');
  }
  
  module.exports = createServiceFactory(Static);
  