module Wukong
  module Datatypes
    #
    # Infinity is bigger than any number
    #
    #
    Infinity = 1.0/0


    #
    # A simple enumerated class
    #
    #   class MyEnum < Enum
    #     enumerates :firefox, :safari, :ie, :chrome, :other
    #   end
    #   MyEnum[1].to_s # => "safari"
    #
    #
    class Enum
      attr_accessor            :val
      class_inheritable_accessor :names
      def initialize val
        self.val = val
      end
      # MyEnum[val] is sugar for MyEnum.new(val)
      def self.[] *args
        new *args
      end

      # returns the value corresponding to that string representation
      def index *args
        # delegate
        self.class.names.index *args
      end

      # Representations:
      def to_i
        val
      end
      def to_s
        return nil if val.nil?
        self.class.names[val]
      end

      def inspect
        "<#{self.class.to_s} #{to_i} (#{to_s})>"
      end

      def to_flat
        to_s #to_i
      end

      def self.to_sql_str
        "ENUM('#{names.join("', '")}')"
      end

      def self.to_pig
        'chararray'
      end

      #
      # Use enumerates to set the class' names
      #
      #   class MyEnum < Enum
      #     enumerates :firefox, :safari, :ie, :chrome, :other
      #   end
      #   MyEnum[1].to_s # => "safari"
      #
      #
      def self.enumerates *names
        self.names = names.map(&:to_s)
      end
    end

    #
    # Note that bin 0 is
    #
    class Binned < Enum
      class_inheritable_accessor :bins, :empty_bin_name
      @@empty_bin_name = '(none)'

      def bins
        self.class.bins
      end

      # FIXME -- doesn't respect a lower bound.
      def initialize val
        return super(val) if val.nil?
        last_top = bins.first
        bins.each_with_index do |bin_top, idx|
          return super(idx) if val <= bin_top
        end
        return super(bins.length)
      end

      def self.enumerates *bins
        options = bins.extract_options!
        self.bins = bins
        last_top = bins.shift
        # bins.unshift bins.first if last_top == -Infinity
        names = bins.map do |bin_top|
          name = bin_name last_top, bin_top, options
          last_top = (last_top.is_a?(Integer) ? bin_top + 1 : bin_top)
          name
        end
        super(*names)
      end

      #
      # Bins
      #
      def self.bin_name lo_val, hi_val, options = { }
        # case lo_val
        # when Integer then lo_val = [lo_val+1, hi_val].compact.min
        # end
        case
        when lo_val == -Infinity then "< #{hi_val}"
        when hi_val ==  Infinity then "#{lo_val}+"
        when (lo_val == hi_val)  then    lo_val
        else                         "#{lo_val} - #{hi_val}"
        end
      end

    end
  end
end

