module Clockwork
  class At
    class FailedToParse < StandardError; end

    NOT_SPECIFIED = nil
    WDAYS = %w[sunday monday tuesday wednesday thursday friday saturday].each.with_object({}).with_index do |(w, wdays), index|
      [w, w.capitalize, w[0...3], w[0...3].capitalize].each do |k|
        wdays[k] = index
      end
    end

    def self.parse(at)
      return unless at
      case at
      when /\A([[:alpha:]]+)\s\*{1,2}:\*{1,2}\z/
        if wday = WDAYS[$1]
          new(NOT_SPECIFIED, NOT_SPECIFIED, wday)
        else
          raise FailedToParse, at
        end
      when /\A([[:alpha:]]+)\s(.*)\z/
        if wday = WDAYS[$1]
          parsed_time = parse($2)
          parsed_time.wday = wday
          parsed_time
        else
          raise FailedToParse, at
        end
      when /\A(\d{1,2}):(\d\d)\z/
        new($2.to_i, $1.to_i)
      when /\A\*{1,2}:(\d\d)\z/
        new($1.to_i)
      when /\A(\d{1,2}):\*\*\z/
        new(NOT_SPECIFIED, $1.to_i)
      else
        raise FailedToParse, at
      end
    rescue ArgumentError
      raise FailedToParse, at
    end

    attr_accessor :min, :hour, :wday

    def initialize(min, hour=NOT_SPECIFIED, wday=NOT_SPECIFIED)
      @min = min
      @hour = hour
      @wday = wday
      raise ArgumentError unless valid?
    end

    def ready?(t)
      (@min == NOT_SPECIFIED or t.min == @min) and
        (@hour == NOT_SPECIFIED or t.hour == @hour) and
        (@wday == NOT_SPECIFIED or t.wday == @wday)
    end

    def == other
      @min == other.min && @hour == other.hour && @wday == other.wday
    end

    private
    def valid?
      @min == NOT_SPECIFIED || (0..59).cover?(@min) &&
        @hour == NOT_SPECIFIED || (0..23).cover?(@hour) &&
        @wday == NOT_SPECIFIED || (0..6).cover?(@wday)
    end
  end
end
