# An XSD duration
module RdfContext
  class Duration
    attr_accessor :ne, :yr, :mo, :da, :hr, :mi, :se
  
    # * Given an integer, assumes that it is milliseconds
    # * Given a time, extract second
    # * Given a Flaat, use value direcly
    # * Given a String, parse as xsd:duration
    # @param [Integer, Time, Float, String] value (#to_i)
    # @return [Duration]
    def initialize(value)
      case value
      when Hash
        @ne = value[:ne] || 1
        @yr = value[:yr] || value[:years] || 0
        @mo = value[:mo] || value[:months] || 0
        @da = value[:da] || value[:days] || 0
        @hr = value[:hr] || value[:hours] || 0
        @mi = value[:mi] || value[:minutes] || 0
        @se = value[:se] || value[:seconds] || 0
      when Duration
        @se = value.to_f
      when Numeric
        @se = value
      else
        @se = value.to_i
      end
      
      self.normalize
    end
  
  
    # Reverse convert from XSD version of duration
    # XSD allows -P1111Y22M33DT44H55M66.666S with any combination in regular order
    # We assume 1M == 30D, but are out of spec in this regard
    # We only output up to hours
    #
    # @param [String] value XSD formatted duration
    # @return [Duration]
    def self.parse(value)
      if value.to_s.match(/^(-?)P(\d+Y)?(\d+M)?(\d+D)?T?(\d+H)?(\d+M)?([\d\.]+S)?$/)
        hash = {}
        hash[:ne] = $1 == "-" ? -1 : 1
        hash[:yr] = $2.to_i
        hash[:mo] = $3.to_i
        hash[:da] = $4.to_i
        hash[:hr] = $5.to_i
        hash[:mi] = $6.to_i
        hash[:se] = $7.to_f
        value = hash
      end

      self.new(value)
    end
    
    # @return [Float]
    def to_f
      (((((@yr.to_i * 12 + @mo.to_i) * 30 + @da.to_i) * 24 + @hr.to_i) * 60 + @mi.to_i) * 60 + @se.to_f) * (@ne || 1)
    end
    
    # @return [Integer]
    def to_i; Integer(self.to_f); end
    
    # @param [Duration, String, Numeric] something (false)
    # @return [Boolean]
    def eql?(something)
      case something
      when Duration
        self.to_f == something.to_f
      when String
        self.to_s(:xml) == something
      when Numeric
        self.to_f == something
      else
        false
      end
    end
    alias_method :==, :eql?
   
    # @param [Symbol] format Output format, :xml outputs in xmlschema mode, otherwise in Human form
    # @return [String]
    def to_s(format = nil)
      usec = (@se * 1000).to_i % 1000
      sec_str = usec > 0 ? "%2.3f" % @se : @se.to_i.to_s
      
      if format == :xml
        str = @ne < 0 ? "-P" : "P"
        str << "%dY" % @yr if @yr > 0
        str << "%dM" % @mo if @mo > 0
        str << "%dD" % @da if @da > 0
        str << "T" if @hr + @mi + @se > 0
        str << "%dH" % @hr if @hr > 0
        str << "%dM" % @mi if @mi > 0
        str << "#{sec_str}S" if @se > 0
      else
        ar = []
        ar << "%d years"    % @yr     if @yr > 0
        ar << "%d months"   % @mo     if @mo > 0
        ar << "%d days"     % @da     if @da > 0
        ar << "%d hours"    % @hr     if @hr > 0
        ar << "%d minutes"  % @mi     if @mi > 0
        ar << "%s seconds"  % sec_str if @se > 0
        last = ar.pop
        first = ar.join(", ")
        res = first.empty? ? last : "#{first} and #{last}"
        ne < 0 ? "#{res} ago" : res
      end
    end
    
    protected
    
      # Normalize representation by adding everything up and then breaking it back down again
      def normalize
        s = self.to_f
        
        @ne = s < 0 ? -1 : 1
        s = s * @ne
        _mi, @se = s.divmod(60)
        _hr, @mi = _mi.to_i.divmod(60)
        _da, @hr = _hr.divmod(24)
        _mo, @da = _da.divmod(30)
        @yr, @mo = _mo.divmod(12)
      end
  end
end