# frozen_string_literal: true

module PrometheusExporter::Metric
  class Histogram < Base

    DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5.0, 10.0].freeze

    @default_buckets = nil if !defined?(@default_buckets)

    def self.default_buckets
      @default_buckets || DEFAULT_BUCKETS
    end

    def self.default_buckets=(buckets)
      @default_buckets = buckets
    end

    attr_reader :buckets

    def initialize(name, help, opts = {})
      super(name, help)
      @buckets = (opts[:buckets] || self.class.default_buckets).sort
      reset!
    end

    def reset!
      @sums = {}
      @counts = {}
      @observations = {}
    end

    def to_h
      data = {}
      @observations.each do |labels, buckets|
        count = @counts[labels]
        sum = @sums[labels]
        data[labels] = { "count" => count, "sum" => sum }
      end
      data
    end

    def remove(labels)
      @observations.delete(labels)
      @counts.delete(labels)
      @sums.delete(labels)
    end

    def type
      "histogram"
    end

    def metric_text
      text = +""
      first = true
      @observations.each do |labels, buckets|
        text << "\n" unless first
        first = false
        count = @counts[labels]
        sum = @sums[labels]
        @buckets.each do |bucket|
          value = @observations[labels][bucket]
          text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, bucket.to_s))} #{value}\n"
        end
        text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, "+Inf"))} #{count}\n"
        text << "#{prefix(@name)}_count#{labels_text(labels)} #{count}\n"
        text << "#{prefix(@name)}_sum#{labels_text(labels)} #{sum}"
      end
      text
    end

    def observe(value, labels = nil)
      labels ||= {}
      buckets = ensure_histogram(labels)

      value = value.to_f
      @sums[labels] += value
      @counts[labels] += 1

      fill_buckets(value, buckets)
    end

    def ensure_histogram(labels)
      @sums[labels] ||= 0.0
      @counts[labels] ||= 0
      buckets = @observations[labels]
      if buckets.nil?
        buckets = @buckets.map { |b| [b, 0] }.to_h
        @observations[labels] = buckets
      end
      buckets
    end

    def fill_buckets(value, buckets)
      @buckets.reverse_each do |b|
        break if value > b
        buckets[b] += 1
      end
    end

    def with_bucket(labels, bucket)
      labels.merge("le" => bucket)
    end

  end
end