require_relative '../../lib/carto/table_utils'
require_dependency 'carto/bounding_box_utils'

class Carto::BoundingBoxService
  include Carto::TableUtils
  include ::LoggerHelper

  def initialize(user, table_name)
    @user = user
    @table_name = table_name
  end

  def table_bounds
    # (lon,lat) as comes out from postgis
    result = current_bbox_using_stats
    return nil unless result
    {
      maxx: Carto::BoundingBoxUtils.bound_for(result[:max][0].to_f, :minx, :maxx),
      maxy: Carto::BoundingBoxUtils.bound_for(result[:max][1].to_f, :miny, :maxy),
      minx: Carto::BoundingBoxUtils.bound_for(result[:min][0].to_f, :minx, :maxx),
      miny: Carto::BoundingBoxUtils.bound_for(result[:min][1].to_f, :miny, :maxy)
    }
  end

  private

  # Postgis stats-based calculation of bounds. Much faster but not always present, so needs a fallback
  def current_bbox_using_stats
    # Less ugly (for tests at least) than letting an exception generate not having any table name
    return nil unless @table_name.present?

    # (lon,lat) as comes from postgis
    JSON.parse(@user.db_service.execute_in_user_database(%{
      SELECT _postgis_stats ('#{@table_name}', 'the_geom');
    }).first['_postgis_stats'])['extent'].symbolize_keys
  rescue PG::Error => e
    if e.message =~ /stats for (.*) do not exist/i
      begin
        current_bbox
      rescue StandardError
        nil
      end
    end
  end

  def current_bbox
    # (lon, lat) as comes from postgis (ST_X = Longitude, ST_Y = Latitude)
    # map has no geometries
    result = get_bbox_values
    if result.all? { |_, value| value.nil? }
      nil
    else
      # still (lon,lat) to be consistent with current_bbox_using_stats()
      {
        max: [result['maxx'].to_f, result['maxy'].to_f],
        min: [result['minx'].to_f, result['miny'].to_f]
      }
    end
  rescue PG::Error => exception
    log_error(exception: exception, table: { name: @table_name })
    raise BoundingBoxError.new("Can't calculate the bounding box for table #{@table_name}. ERROR: #{exception}")
  end

  def get_bbox_values
    result = @user.db_service.execute_in_user_database(%{
      SELECT
        ST_XMin(ST_Extent(the_geom)) AS minx,
        ST_YMin(ST_Extent(the_geom)) AS miny,
        ST_XMax(ST_Extent(the_geom)) AS maxx,
        ST_YMax(ST_Extent(the_geom)) AS maxy
      FROM #{safe_table_name_quoting(@table_name)} AS subq
    }).first

    result
  end
end
