module Barometer
  #
  # = Yahoo! Weather
  # www.yahoo.com
  #
  # - key required: NO
  # - registration required: NO
  # - supported countries: US (by zipcode), International (by Yahoo Location ID)
  #
  # === performs geo coding
  # - city: YES
  # - coordinates: YES
  #
  # === time info
  # - sun rise/set: YES (today only)
  # - provides timezone: PARTIAL (just short code)
  # - requires TZInfo: NO
  #
  # == resources
  # - API: http://developer.yahoo.com/weather/
  #
  # === Possible queries:
  # - http://weather.yahooapis.com/forecastrss?p=94089
  # - http://weather.yahooapis.com/forecastrss?p=USCA1116
  # - http://weather.yahooapis.com/forecastrss?p=FRXX0076&u=c
  #
  # where query can be:
  # - zipcode (US)
  # - Yahoo! Location ID [actually weather.com id] (International)
  #
  # = Yahoo! terms of use
  # The feeds are provided free of charge for use by individuals and non-profit
  # organizations for personal, non-commercial uses. We ask that you provide
  # attribution to Yahoo! Weather in connection with your use of the feeds.
  # If you provide this attribution in text, please use: "Yahoo! Weather." If you
  # provide this attribution with a graphic, please use the Yahoo! Weather logo that
  # we have included in the feed itself.
  # We reserve all rights in and to the Yahoo! Weather logo, and your right to use
  # the Yahoo! Weather logo is limited to providing attribution in connection with
  # these RSS feeds. Yahoo! also reserves the right to require you to cease
  # distributing these feeds at any time for any reason.
  #
  # == notes
  # - the Yahoo! Location ID is a propreitary number (shared with weather.com)
  #
  class WeatherService::Yahoo < WeatherService
    
    def self.source_name; :yahoo; end
    def self.accepted_formats; [:zipcode, :weather_id]; end
    
    # these are the icon codes that indicate "wet", used by wet? function
    def self.wet_icon_codes
      codes = [1] + (3..18).to_a + [35] + (37..43).to_a + (45..47).to_a
      codes.collect {|c| c.to_s}
    end
    def self.sunny_icon_codes
      codes = (29..34).to_a + [36]
      codes.collect {|c| c.to_s}
    end

    def self._measure(measurement, query, metric=true)
      raise ArgumentError unless measurement.is_a?(Data::Measurement)
      raise ArgumentError unless query.is_a?(Barometer::Query)
      measurement.source = self.source_name
      
      begin
        result = self.fetch(query.q, metric)
      rescue Timeout::Error => e
        return measurement
      end
      
      measurement.current = self.build_current(result, metric)
      measurement.forecast = self.build_forecast(result, metric)
      measurement.location = self.build_location(result, query.geo)
      
      if result["title"] && result["link"]
        measurement.links[result["title"]] = result["link"]
      end
      
      sun = nil
      if measurement.current
        sun = self.build_sun(result)
        measurement.current.sun = sun
      end
      # use todays sun data for all future days
      if measurement.forecast && sun
        measurement.forecast.each do |forecast|
          forecast.sun = sun
        end
      end
      
      local_time = self.build_local_time(result)
      if local_time
        measurement.measured_at = local_time
        measurement.current.current_at = local_time
      end
      
      measurement
    end
    
def self.build_local_time(data)
  if data
    if data['item']
      # what time is it now?
      now_utc = Time.now.utc
      
      # get published date
      pub_date = data['item']['pubDate']
      
      # get the TIME ZONE CODE
      zone_match = pub_date.match(/ ([A-Z]*)$/) if pub_date
      
      if zone_match
        zone = zone_match[1] 
        
        # try converting pub_date to utc
#        pub_date_utc = Data::Zone.code_to_utc(Time.parse(pub_date), zone) if zone
     
        # how far back was this?
 #       data_age_in_seconds = now_utc - pub_date_utc
      
        # is this older then 2 hours
#        if (data_age_in_seconds < 0) || (data_age_in_seconds > (60 * 60 * 2))
          # we may have converted the time wrong.
          # if pub_date in the future, then?
          # if pub_date too far back, then?
        
          # for now do nothing ... don't set measured_time
#          return nil
#        else
          # everything seems fine
          # convert now to the local time 
          offset = Data::Zone.zone_to_offset(zone)
          return Data::LocalTime.parse(now_utc + offset)
#        end
      end
      nil
    end
  end
end
    
    def self.build_current(data, metric=true)
      raise ArgumentError unless data.is_a?(Hash)
      current = Data::CurrentMeasurement.new
      if data
        if data['item'] && data['item']['yweather:condition']
          condition_result = data['item']['yweather:condition']
          current.updated_at = Data::LocalDateTime.parse(condition_result['date'])
          current.icon = condition_result['code']
          current.condition = condition_result['text']
          current.temperature = Data::Temperature.new(metric)
          current.temperature << condition_result['temp']
        end
        if data['yweather:atmosphere']
          atmosphere_result = data['yweather:atmosphere']
          current.humidity = atmosphere_result['humidity'].to_i
          current.pressure = Data::Pressure.new(metric)
          current.pressure << atmosphere_result['pressure']
          current.visibility = Data::Distance.new(metric)
          current.visibility << atmosphere_result['visibility']
        end
        if data['yweather:wind']
          wind_result = data['yweather:wind']
          current.wind = Data::Speed.new(metric)
          current.wind << wind_result['speed']
          current.wind.degrees = wind_result['degrees'].to_f
          current.wind_chill = Data::Temperature.new(metric)
          current.wind_chill << wind_result['chill']
        end
      end
      current
    end
    
    def self.build_forecast(data, metric=true)
      raise ArgumentError unless data.is_a?(Hash)
      forecasts = []
      
      if data && data['item'] && data['item']['yweather:forecast']
         forecast_result = data['item']['yweather:forecast']
         
        forecast_result.each do |forecast|
          forecast_measurement = Data::ForecastMeasurement.new
          forecast_measurement.icon = forecast['code']
          forecast_measurement.date = Date.parse(forecast['date'])
          forecast_measurement.condition = forecast['text']
          forecast_measurement.high = Data::Temperature.new(metric)
          forecast_measurement.high << forecast['high'].to_f
          forecast_measurement.low = Data::Temperature.new(metric)
          forecast_measurement.low << forecast['low'].to_f
          forecasts << forecast_measurement
        end
      end
      forecasts
    end
    
    def self.build_location(data, geo=nil)
      raise ArgumentError unless data.is_a?(Hash)
      raise ArgumentError unless (geo.nil? || geo.is_a?(Data::Geo))
      location = Data::Location.new
      # use the geocoded data if available, otherwise get data from result
      if geo
        location.city = geo.locality
        location.state_code = geo.region
        location.country = geo.country
        location.country_code = geo.country_code
        location.latitude = geo.latitude
        location.longitude = geo.longitude
      else
        if data && data['yweather:location']
          location.city = data['yweather:location']['city']
          location.state_code = data['yweather:location']['region']
          location.country_code = data['yweather:location']['country']
          if data['item']
            location.latitude = data['item']['geo:lat']
            location.longitude = data['item']['geo:long']
          end
        end
      end
      location
    end
    
    def self.build_sun(data)
      raise ArgumentError unless data.is_a?(Hash)
      sun = nil
      if data && data['yweather:astronomy'] && data['item']
        local_rise = Data::LocalTime.parse(data['yweather:astronomy']['sunrise'])
        local_set = Data::LocalTime.parse(data['yweather:astronomy']['sunset'])
        sun = Data::Sun.new(local_rise, local_set)
      end
      sun || Data::Sun.new
    end

    # use HTTParty to get the current weather
    def self.fetch(query, metric=true)
      self.get(
        "http://weather.yahooapis.com/forecastrss",
        :query => {:p => query, :u => (metric ? 'c' : 'f')},
        :format => :xml,
        :timeout => Barometer.timeout
      )['rss']['channel']
    end
    
  end
end