grammar Deeds::Grammar

rule root
  (date interval? durations? activity?) {
    def date
      capture(:date).value
    end

    def minutes
      result = []
      result.push(capture(:interval).try!(:minutes) || 0)
      result.push(capture(:durations).try!(:minutes) || 0)
      result.sum
    end

    def activity
      capture(:activity).value
    end
  }
end

# What you did during that time is entirely freetext
#
rule activity
  (.*) {
    to_str.presence
  }
end

# A Date in the format `yyyy-mm-dd`.
# The `#value` is a `Date` instance if it's a valid calendar date.
#
rule date
  (number "-" number "-" number) {
    begin
      Date.parse to_str
    rescue ArgumentError
      exception = Citrus::ParseError.new input
      exception.message << ' This is not a valid date'
      raise exception
    end
  }
end

# An Interval is the difference between one time and another
# during the same day, separated by a dash. Whitespace is ignored.
# The `#minutes` is the difference in minutes or nil if invalid.
#
# Examples
#
#   `8:00-17:00`
#   `3:5 - 23:07`
#
rule interval
  (from:time "-" till:time) {
    def minutes
      return if capture(:till).minutes.nil?
      return if capture(:from).minutes.nil?
      capture(:till).minutes - capture(:from).minutes
    end
  }
end

# A Timestamp is two digits separated by a colon.
# It can only do military time and it ignores whitespace.
# `#minutes` contains the number of seconds since midnight or nil.
#
# Examples
#
#   `7:30`
#   `  23  :  50  `
#
rule time
  (number ":" number) {
    def minutes
      Time.parse(to_str).seconds_since_midnight.to_i / 60
    rescue ArgumentError
      exception = Citrus::ParseError.new input
      exception.message << ' This is not a valid time'
      raise exception
    end
  }
end

# One or more durations.
# This is just a wrapper for `multiple_durations`.
# I'm not entirely sure how to refactor this away.
#
# Examples
#
#   `1m`
#   `7h`
#   `4m 6m 3h`
#
rule durations
  multiple_durations | duration
end

# Two or more durations.
# The total minutes are summed up in `#minutes`.
#
# Examples
#
#   `3m -4m`
#   `5h 2m +6m 20m`
#
rule multiple_durations
  (duration more:(multiple_durations | duration)) {
    def minutes
      [capture(:duration).minutes, capture(:more).minutes].sum
    end
  }
end

# Duration is either a minute or an hour.
# The number of minutes are exposed via `#minutes`.
#
# Examples
#
#   `3m`
#   `5h`
#
rule duration
  minute | hour
end

# A minute is a digit immediately follwed by the letter `m`.
# Whitespace is only allowed before the digit and after the `m`.
#
# Examples
#
#   `3m`
#   `  3m   `
#
rule minute
  (number "m" whitespace) {
    def minutes
      capture(:number).value
    end
  }
end

# An hour is a digit immediately follwed by the letter `h`.
# Whitespace is only allowed before the digit and after the `h`.
# Hours are broken down into minutes by `#minutes`
#
# Examples
#
#   `3h`
#   `  3h   `
#
rule hour
  (number "h" whitespace) {
    def minutes
      capture(:number).value * 60
    end
  }
end

# A number can optionally have a minus or a plus as prefix.
# Whitespace is allowed, even between that prefix and the digit.
#
rule number
  (whitespace [\-\+]? whitespace [0-9]+ whitespace) {
    to_str.gsub(/[^\-\+\d]*/, '').to_i
  }
end

rule whitespace
  [\s]*
end

end