# encoding: UTF-8
# frozen_string_literal: true

module TZInfo
  # A subclass of {Timestamp} used to represent local times.
  # {TimestampWithOffset} holds a reference to the related {TimezoneOffset} and
  # overrides various methods to return results appropriate for the
  # {TimezoneOffset}. Certain operations will clear the associated
  # {TimezoneOffset} (if the {TimezoneOffset} would not necessarily be valid for
  # the result). Once the {TimezoneOffset} has been cleared,
  # {TimestampWithOffset} behaves identically to {Timestamp}.
  class TimestampWithOffset < Timestamp
    include WithOffset

    # @return [TimezoneOffset] the {TimezoneOffset} associated with this
    #   instance.
    attr_reader :timezone_offset

    # Creates a new {TimestampWithOffset} from a given {Timestamp} and
    # {TimezoneOffset}.
    #
    # @param timestamp [Timestamp] a {Timestamp}.
    # @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the
    # time of `timestamp`.
    # @return [TimestampWithOffset] a {TimestampWithOffset} that has the same
    #   {value value} and {sub_second sub_second} as the `timestamp` parameter,
    #   a {utc_offset utc_offset} equal to the
    #   {TimezoneOffset#observed_utc_offset observed_utc_offset} of the
    #   `timezone_offset` parameter and {timezone_offset timezone_offset} set to
    #   the `timezone_offset` parameter.
    # @raise [ArgumentError] if `timestamp` or `timezone_offset` is `nil`.
    def self.set_timezone_offset(timestamp, timezone_offset)
      raise ArgumentError, 'timestamp must be specified' unless timestamp
      raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset
      RDL.type_cast(new!(timestamp.value, timestamp.sub_second, timezone_offset.observed_utc_offset), "TZInfo::TimestampWithOffset").set_timezone_offset(timezone_offset)
    end

    # Sets the associated {TimezoneOffset} of this {TimestampWithOffset}.
    #
    # @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the time
    #   and for the offset of this {TimestampWithOffset}.
    # @return [TimestampWithOffset] `self`.
    # @raise [ArgumentError] if `timezone_offset` is `nil`.
    # @raise [ArgumentError] if {utc? self.utc?} is `true`.
    # @raise [ArgumentError] if `timezone_offset.observed_utc_offset` does not equal
    #   `self.utc_offset`.
    def set_timezone_offset(timezone_offset)
      raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset
      raise ArgumentError, 'timezone_offset.observed_utc_offset does not match self.utc_offset' if utc? || utc_offset != timezone_offset.observed_utc_offset
      @timezone_offset = timezone_offset
      self
    end

    # An overridden version of {Timestamp#to_time} that, if there is an
    # associated {TimezoneOffset}, returns a {TimeWithOffset} with that offset.
    #
    # @return [Time] if there is an associated {TimezoneOffset}, a
    #   {TimeWithOffset} representation of this {TimestampWithOffset}, otherwise
    #   a `Time` representation.
    def to_time
      to = timezone_offset
      if to
        new_time(TimeWithOffset).set_timezone_offset(to)
      else
        super
      end
    end

    # An overridden version of {Timestamp#to_datetime}, if there is an
    # associated {TimezoneOffset}, returns a {DateTimeWithOffset} with that
    # offset.
    #
    # @return [DateTime] if there is an associated {TimezoneOffset}, a
    #   {DateTimeWithOffset} representation of this {TimestampWithOffset},
    #   otherwise a `DateTime` representation.
    def to_datetime
      to = timezone_offset
      if to
        new_datetime(DateTimeWithOffset).set_timezone_offset(to)
      else
        super
      end
    end
  end
end
