require 'haversine'
require 'pr_geohash'

class ProximityHash

  MIN_PRECISION = 1
  MAX_PRECISION = 12

  EARTH_RADIUS = 6371000

  def initialize(latitude, longitude, radius, precision)
    @centre = [ latitude, longitude ]
    @radius = radius
    @precision = precision
    if ((precision > MAX_PRECISION) or (precision < MIN_PRECISION))
      raise 'Precision out bounds'
    end
  end

  def calculate
    x = 0.0
    y = 0.0

    points = []
    geohashes = {}

    grid_width = [ 5009400.0, 1252300.0, 156500.0, 39100.0, 4900.0, 1200.0, 152.9, 38.2, 4.8, 1.2, 0.149, 0.0370 ]
    grid_height = [ 4992600.0, 624100.0, 156000.0, 19500.0, 4900.0, 609.4, 152.4, 19.0, 4.8, 0.595, 0.149, 0.0199 ]

    height = (grid_height[@precision - 1]) / 2.0
    width = (grid_width[@precision - 1]) / 2.0

    latitude = @centre[0]
    longitude = @centre[1]

    lat_moves = (@radius / height).ceil
    lon_moves = (@radius / width).ceil

    (0 .. lat_moves).each do |i|

      temp_lat = y + (height * i)

      (0..lon_moves).each do |j|

        temp_lon = x + (width * j)

        if within_radius(temp_lat, temp_lon, y, x) == true

          x_cen, y_cen = get_centre(temp_lat, temp_lon, height, width)
          lat, lon = convert_to_latlon(y_cen, x_cen, latitude, longitude)
          points += [[lat, lon]]
          lat, lon = convert_to_latlon(-y_cen, x_cen, latitude, longitude)
          points += [[lat, lon]]
          lat, lon = convert_to_latlon(y_cen, -x_cen, latitude, longitude)
          points += [[lat, lon]]
          lat, lon = convert_to_latlon(-y_cen, -x_cen, latitude, longitude)
          points += [[lat, lon]]
        end

      end

    end

    points.each do |point|
      geohash = GeoHash.encode(point[0], point[1], @precision)
      geohashes[geohash] = Haversine.distance(point, @centre).to_meters
    end

    geohashes.sort_by { |key, value| value }.to_h
  end

  private

  def within_radius(latitude, longitude, centre_lat, centre_lon)

    x_diff = longitude - centre_lon
    y_diff = latitude - centre_lat

    return (((x_diff ** 2) + (y_diff ** 2)) <= (@radius ** 2))
  end

  def get_centre(latitude, longitude, height, width)

    y_cen = latitude + (height / 2.0)
    x_cen = longitude + (width / 2.0)

    return x_cen, y_cen
  end

  def convert_to_latlon(y, x, latitude, longitude)
     lat_diff = (y / EARTH_RADIUS) * (180.0 / Math::PI)
    lon_diff = (x / EARTH_RADIUS) * (180.0 / Math::PI) / Math.cos(latitude * Math::PI/180.0)

    final_lat = latitude + lat_diff
    final_lon = longitude + lon_diff

    return final_lat, final_lon
  end

end