module Hijri
  class Date
    include Comparable

    attr_reader :day, :month, :year

    alias :mon  :month
    alias :mday :day

    MONTHNAMES = [nil] + %w(Muharram Safar Rabia-Awwal Rabia-Thani Jumaada-Awal Jumaada-Thani Rajab Sha'ban Ramadan Shawwal Dhul-Qi'dah Dhul-Hijjah)
    DAYNAMES = %w(as-Sabt al-Ahad al-Ithnayn ath-Thalaathaa al-Arba'aa' al-Khamis al-Jumu'ah)
    ABBR_MONTHNAMES = [nil] + ["Muharram", "Safar", "Rabia I", "Rabia II", "Jumaada I", "Jumaada II", "Rajab", "Sha'ban", "Ramadan", "Shawwal", "Dhul-Qi'dah", "Dhul-Hijjah"]
    ABBR_DAYNAMES = %w(Sabt Ahad Ithnayn Thalaathaa Arba'aa' Khamis Jumu'ah)

    [MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYNAMES].each do |xs|
      xs.each{|x| x.freeze unless x.nil?}.freeze
    end

    HALF_DAYS_IN_DAY       = Rational(1, 2) # :nodoc:
    HOURS_IN_DAY           = Rational(1, 24) # :nodoc:
    MINUTES_IN_DAY         = Rational(1, 1440) # :nodoc:
    SECONDS_IN_DAY         = Rational(1, 86400) # :nodoc:
    MILLISECONDS_IN_DAY    = Rational(1, 86400*10**3) # :nodoc:
    NANOSECONDS_IN_DAY     = Rational(1, 86400*10**9) # :nodoc:
    MILLISECONDS_IN_SECOND = Rational(1, 10**3) # :nodoc:
    NANOSECONDS_IN_SECOND  = Rational(1, 10**9) # :nodoc:

    class << self
      def today
        date = ::Date.today
        date.to_hijri
      end
    end

    def initialize(year=1, month=1, day=1)
      if valid_date?(year, month, day)
        @year, @month, @day = year, month, day
      else
        raise ArgumentError, "Invalid Date"
      end
    end

    def change(kargs)
      # Remove nil values
      kargs.reject!{|k,v| v.nil?}
      @year  = kargs.fetch :year , year
      @month = kargs.fetch :month, month
      @day   = kargs.fetch :day  , day
    end

    def to_s
      format('%.4d-%02d-%02d', year, month, day)
    end

    def <=>(date)
      # Make sure the date is a Hijri::Date instance
      date = date.to_hijri
      if self.to_s == date.to_s
        return 0
      elsif @year > date.year ||
           (@year == date.year && @month > date.month) ||
           (@year == date.year && @month == date.month && @day > date.day)
        return 1
      else
        return -1
      end
    end

    def to_abs
      month_days = 29 * (month - 1) # days on this year
      nonleap_year_days  = 354 * (year - 1)
      leap_year_days = (3 + (11 * year)) / 30.0
      this_year  = (month / 2.0).to_i

      return (day + month_days + this_year + nonleap_year_days + leap_year_days + Hijri::ISLAMIC_EPOCH).to_i
    end
    alias :abs :to_abs

    def to_greo(adjust=0)
      ::Date.new(*Converter.hijri_to_greo(self)) + adjust
    end

    # Just to have a consistent Interface.
    def to_hijri
      self
    end

    def valid_date?(year, month, day)
      return false unless (1..INFINITY).cover?(year)
      return false unless (1..12).cover?(month)
      return false unless (1..30).cover?(day)
      return true
    end

    def yday
      (((month - 1) * AVERAGE_MONTH_DAYS) + day).floor
    end

    def wday
      (((year * AVERAGE_YEARS_DAYS) + yday) % 7).floor
    end

    def weeknum(week_start=0)
      yday / 7
    end
    alias :cweek :weeknum

    def wnum0() weeknum(0) end # :nodoc:
    def wnum1() weeknum(1) end # :nodoc:

    private :wnum0, :wnum1
  end
end
