module Barometer
  #
  # A simple Temperature class
  #
  # Think of this like the Integer class. Enhancement
  # is that you can create a number (in a certain unit), then
  # get that number back already converted to another unit.
  #
  # All comparison operations will be done in the absolute
  # scale of Kelvin (K)
  #
  class Data::Temperature < Data::Units

    METRIC_UNITS = "C"
    IMPERIAL_UNITS = "F"

    attr_accessor :celsius, :fahrenheit, :kelvin

    def initialize(metric=true)
      @celsius = nil
      @fahrenheit = nil
      @kelvin = nil
      super(metric)
    end

    def metric_default=(value); self.c = value; end
    def imperial_default=(value); self.f = value; end

    #
    # CONVERTERS
    #

    def self.c_to_k(c)
      return nil unless c && (c.is_a?(Integer) || c.is_a?(Float))
      273.15 + c.to_f
    end

    # Tf = (9/5)*Tc+32
    #
    def self.c_to_f(c)
      return nil unless c && (c.is_a?(Integer) || c.is_a?(Float))
       ((9.0/5.0)*c.to_f)+32.0
    end

    # Tc = (5/9)*(Tf-32)
    #
    def self.f_to_c(f)
      return nil unless f && (f.is_a?(Integer) || f.is_a?(Float))
      (5.0/9.0)*(f.to_f-32.0)
    end

    def self.f_to_k(f)
      return nil unless f && (f.is_a?(Integer) || f.is_a?(Float))
      c = self.f_to_c(f.to_f)
      self.c_to_k(c)
    end

    def self.k_to_c(k)
      return nil unless k && (k.is_a?(Integer) || k.is_a?(Float))
      k.to_f - 273.15
    end

    def self.k_to_f(k)
      return nil unless k && (k.is_a?(Integer) || k.is_a?(Float))
      c = self.k_to_c(k.to_f)
      self.c_to_f(c)
    end

    #
    # ACCESSORS
    #

    # store celsius and kelvin
    #
    def c=(c)
      return if !c || !(c.is_a?(Integer) || c.is_a?(Float))
      @celsius = c.to_f
      @kelvin = Data::Temperature.c_to_k(c.to_f)
      self.update_fahrenheit(c.to_f)
    end

    # store fahrenheit and kelvin
    #
    def f=(f)
      return if !f || !(f.is_a?(Integer) || f.is_a?(Float))
      @fahrenheit = f.to_f
      @kelvin = Data::Temperature.f_to_k(f.to_f)
      self.update_celsius(f.to_f)
    end

    # store kelvin, convert to all
    #
    def k=(k)
      return if !k || !(k.is_a?(Integer) || k.is_a?(Float))
      @kelvin = k.to_f
      @celsius = Data::Temperature.k_to_c(k.to_f)
      @fahrenheit = Data::Temperature.k_to_f(k.to_f)
    end

    # return the stored celsius or convert from Kelvin
    #
    def c(as_integer=true)
      c = (@celsius || Data::Temperature.k_to_c(@kelvin))
      c ? (as_integer ? c.to_i : (100*c).round/100.0) : nil
    end

    # return the stored fahrenheit or convert from Kelvin
    #
    def f(as_integer=true)
      f = (@fahrenheit || Data::Temperature.k_to_f(@kelvin))
      f ? (as_integer ? f.to_i : (100*f).round/100.0) : nil
    end

    #
    # OPERATORS
    #

    def <=>(other)
      @kelvin <=> other.kelvin
    end

    #
    # HELPERS
    #

    # will just return the value (no units)
    #
    def to_i(metric=nil)
      (metric || (metric.nil? && self.metric?)) ? self.c : self.f
    end

    # will just return the value (no units) with more precision
    #
    def to_f(metric=nil)
      (metric || (metric.nil? && self.metric?)) ? self.c(false) : self.f(false)
    end

    # will return the value with units
    #
    def to_s(metric=nil)
      (metric || (metric.nil? && self.metric?)) ? "#{self.c} #{METRIC_UNITS}" : "#{self.f} #{IMPERIAL_UNITS}"
    end

    # will just return the units (no value)
    #
    def units(metric=nil)
      (metric || (metric.nil? && self.metric?)) ? METRIC_UNITS : IMPERIAL_UNITS
    end

    # when we set fahrenheit, it is possible the a non-equivalent value of
    # celsius remains.  if so, clear it.
    #
    def update_celsius(f)
      return unless @celsius
      difference = Data::Temperature.f_to_c(f.to_f) - @celsius
      # only clear celsius if the stored celsius is off be more then 1 degree
      # then the conversion of fahrenheit
      @celsius = nil unless difference.abs <= 1.0
    end

    # when we set celsius, it is possible the a non-equivalent value of
    # fahrenheit remains.  if so, clear it.
    #
    def update_fahrenheit(c)
      return unless @fahrenheit
      difference = Data::Temperature.c_to_f(c.to_f) - @fahrenheit
      # only clear fahrenheit if the stored fahrenheit is off be more then 1 degree
      # then the conversion of celsius
      @fahrenheit = nil unless difference.abs <= 1.0
    end

    def nil?
      (@celsius || @fahrenheit || @kelvin) ? false : true
    end

  end
end