module Geokit
  module Geocoders
    # Mapbox geocoder implementation.  Requires the Geokit::Geocoders::MapboxGeocoder:key variable to
    # contain a Mapbox access token.  Conforms to the interface set by the Geocoder class.
    class MapboxGeocoder < Geocoder
      config :key
      self.secure = true

      private

      # Template method which does the reverse-geocode lookup.
      def self.do_reverse_geocode(latlng, _options = nil)
        latlng = LatLng.normalize(latlng)
        url =  "#{protocol}://api.tiles.mapbox.com/v4/geocode/mapbox.places-v1/"
        url += "#{latlng.lng},#{latlng.lat}.json?access_token=#{key}"
        process :json, url
      end

      # Template method which does the geocode lookup.
      def self.do_geocode(address, _options = nil)
        address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
        url =  "#{protocol}://api.tiles.mapbox.com/v4/geocode/mapbox.places-v1/"
        url += "#{Geokit::Inflector.url_escape(address_str)}.json?access_token=#{key}"
        process :json, url
      end

      def self.parse_json(results)
        return GeoLoc.new unless results['features'].count > 0
        loc = nil
        results['features'].each do |feature|
          extracted_geoloc = extract_geoloc(feature)
          if loc.nil?
            loc = extracted_geoloc
          else
            loc.all.push(extracted_geoloc)
          end
        end
        loc
      end

      def self.extract_geoloc(result_json)
        loc = new_loc
        loc.lng = result_json['center'][0]
        loc.lat = result_json['center'][1]
        set_address_components(result_json, loc)
        set_precision(loc)
        set_bounds(result_json['bbox'], loc)
        loc.success = true
        loc
      end

      def self.set_address_components(result_json, loc)
        if result_json['context']
          result_json['context'].each do |context|
            if context['id'] =~ /^country\./
              loc.country = context['text']
            elsif context['id'] =~ /^province\./
              loc.state = context['text']
            elsif context['id'] =~ /^city\./
              loc.city = context['text']
            elsif context['id'] =~ /^postcode/
              loc.zip = context['text']
            end
          end
          loc.country = loc.country_code if loc.country_code && !loc.country
        end
        if result_json['place_name']
          loc.full_address = result_json['place_name']
        end
        if !loc.city && result_json['id'] =~ /^city\./
          loc.city = result_json['text']
        end
        if !loc.state && result_json['id'] =~ /^province\./
          loc.state = result_json['text']
        end
      end

      PRECISION_VALUES = %w(country state city zip)

      def self.set_precision(loc)
        PRECISION_VALUES.each do |precision|
          break unless loc.send(precision) && loc.send(precision).length
          loc.precision = precision
        end
      end

      def self.set_bounds(bounds, loc)
        if bounds
          loc.suggested_bounds = Bounds.normalize([bounds[1], bounds[0]], [bounds[3], bounds[2]])
        end
      end
    end
  end
end
