module Barometer
  #
  # Result Measurement
  # a data class for resulting weather conditions
  #
  # This is basically a data holding class for the resulting weather
  # conditions.
  #
  class Measurement::Result

    attr_reader :current_at, :updated_at
    attr_reader :valid_start_date, :valid_end_date, :date
    attr_reader :humidity, :icon, :condition
    attr_reader :temperature, :dew_point, :heat_index, :wind_chill
    attr_reader :low, :high, :pop
    attr_reader :wind, :sun, :pressure, :visibility
    attr_accessor :metric, :description

    def initialize(metric=true); @metric = metric; end

    # accessors (with input checking)
    #
    def temperature=(temperature)
      raise ArgumentError unless temperature.is_a?(Data::Temperature)
      @temperature = temperature
    end

    def dew_point=(dew_point)
      raise ArgumentError unless dew_point.is_a?(Data::Temperature)
      @dew_point = dew_point
    end

    def heat_index=(heat_index)
      raise ArgumentError unless heat_index.is_a?(Data::Temperature)
      @heat_index = heat_index
    end

    def wind_chill=(wind_chill)
      raise ArgumentError unless wind_chill.is_a?(Data::Temperature)
      @wind_chill = wind_chill
    end

    def pressure=(pressure)
      raise ArgumentError unless pressure.is_a?(Data::Pressure)
      @pressure = pressure
    end

    def visibility=(visibility)
      raise ArgumentError unless visibility.is_a?(Data::Distance)
      @visibility = visibility
    end

    def current_at=(current_at)
      raise ArgumentError unless (current_at.is_a?(Data::LocalTime) || current_at.is_a?(Data::LocalDateTime))
      @current_at = current_at
    end

    def updated_at=(updated_at)
      raise ArgumentError unless (updated_at.is_a?(Data::LocalTime) || updated_at.is_a?(Data::LocalDateTime))
      @updated_at = updated_at
    end

    def date=(date)
      raise ArgumentError unless date.is_a?(Date)
      @date = date
      @valid_start_date = Data::LocalDateTime.new(date.year,date.month,date.day,0,0,0)
      @valid_end_date = Data::LocalDateTime.new(date.year,date.month,date.day,23,59,59)
    end

    def valid_start_date=(date)
      raise ArgumentError unless date.is_a?(Data::LocalDateTime)
      @valid_start_date = date
    end

    def valid_end_date=(date)
      raise ArgumentError unless date.is_a?(Data::LocalDateTime)
      @valid_end_date = date
    end

    def high=(high)
      raise ArgumentError unless high.is_a?(Data::Temperature)
      @high = high
    end

    def low=(low)
      raise ArgumentError unless low.is_a?(Data::Temperature)
      @low = low
    end

    def pop=(pop)
      raise ArgumentError unless pop.is_a?(Fixnum)
      @pop = pop
    end

    def humidity=(humidity)
      raise ArgumentError unless
        (humidity.is_a?(Fixnum) || humidity.is_a?(Float))
      @humidity = humidity
    end

    def icon=(icon)
      raise ArgumentError unless icon.is_a?(String)
      @icon = icon
    end

    def condition=(condition)
      raise ArgumentError unless condition.is_a?(String)
      @condition = condition
    end

    def wind=(wind)
      raise ArgumentError unless wind.is_a?(Data::Speed)
      @wind = wind
    end

    def sun=(sun)
      raise ArgumentError unless (sun.is_a?(Data::Sun) || sun.nil?)
      @sun = sun
    end

    #
    # answer simple questions
    #

    def windy?(threshold=10)
      raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
      return nil unless wind?
      wind.to_f(metric?) >= threshold.to_f
    end

    def day?(time)
      return nil unless time && sun?
      sun.after_rise?(time) && sun.before_set?(time)
    end

    def sunny?(time, sunny_icons=nil)
      return nil unless time
      is_day = day?(time)
      return nil if is_day.nil?
      is_day && _sunny_by_icon?(sunny_icons)
    end

    def wet?(wet_icons=nil, pop_threshold=50, humidity_threshold=99)
      result = nil
      result ||= _wet_by_pop?(pop_threshold) if pop?
      result ||= _wet_by_icon?(wet_icons) if icon?
      result ||= _wet_by_humidity?(humidity_threshold) if humidity?
      result ||= _wet_by_dewpoint? if (dew_point? && temperature?)
      result
    end

    #
    # helpers
    #

    def metric?; metric; end

    def for_datetime?(datetime)
      raise ArgumentError unless datetime.is_a?(Data::LocalDateTime)
      datetime >= @valid_start_date && datetime <= @valid_end_date
    end

    # creates "?" helpers for all attributes (which maps to nil?)
    #
    def method_missing(method,*args)
      # if the method ends in ?, then strip it off and see if we
      # respond to the method without the ?
      if (call_method = method.to_s.chomp!("?")) && respond_to?(call_method)
        return send(call_method).nil? ? false : true
      else
        super(method,*args)
      end
    end

    private

    def _wet_by_dewpoint?
      return nil unless dew_point? && temperature?
      temperature.to_f(metric?) <=  dew_point.to_f(metric?)
    end

    def _wet_by_pop?(threshold=50)
      raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
      return nil unless pop?
      pop.to_f >= threshold.to_f
    end

    def _wet_by_humidity?(threshold=99)
      raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
      return nil unless humidity?
      humidity.to_f >= threshold.to_f
    end

    def _wet_by_icon?(wet_icons=nil)
      raise ArgumentError unless (wet_icons.nil? || wet_icons.is_a?(Array))
      return nil unless (icon? && wet_icons)
      wet_icons.include?(icon.to_s.downcase)
    end

    def _sunny_by_icon?(sunny_icons=nil)
      raise ArgumentError unless (sunny_icons.nil? || sunny_icons.is_a?(Array))
      return nil unless (icon? && sunny_icons)
      sunny_icons.include?(icon.to_s.downcase)
    end

  end
end