# encoding: utf-8
module Mongoid #:nodoc:
  module Fields #:nodoc:
    module Internal #:nodoc:

      # This module contains shared behaviour for date conversions.
      module Timekeeping

        # When reading the field do we need to cast the value? This holds true when
        # times are stored or for big decimals which are stored as strings.
        #
        # @example Typecast on a read?
        #   field.cast_on_read?
        #
        # @return [ true ] Date fields cast on read.
        #
        # @since 2.1.0
        def cast_on_read?; true; end

        # Deserialize this field from the type stored in MongoDB to the type
        # defined on the model.
        #
        # @example Deserialize the field.
        #   field.deserialize(object)
        #
        # @param [ Object ] object The object to cast.
        #
        # @return [ Time ] The converted time.
        #
        # @since 2.1.0
        def deserialize(object)
          return nil if object.blank?
          object = object.getlocal unless Mongoid::Config.use_utc?
          if Mongoid::Config.use_activesupport_time_zone?
            time_zone = Mongoid::Config.use_utc? ? "UTC" : ::Time.zone
            object = object.in_time_zone(time_zone)
          end
          object
        end

        # Special case to serialize the object.
        #
        # @example Convert to a selection.
        #   field.selection(object)
        #
        # @param [ Object ] The object to convert.
        #
        # @return [ Object ] The converted object.
        #
        # @since 2.4.0
        def selection(object)
          return object if object.is_a?(::Hash)
          serialize(object)
        end

        # Serialize the object from the type defined in the model to a MongoDB
        # compatible object to store.
        #
        # @example Serialize the field.
        #   field.serialize(object)
        #
        # @param [ Object ] object The object to cast.
        #
        # @return [ Time ] The converted UTC time.
        #
        # @since 2.1.0
        def serialize(object)
          return nil if object.blank?
          begin
            time = convert_to_time(object)
            strip_milliseconds(time).utc
          rescue ArgumentError
            raise Errors::InvalidTime.new(object)
          end
        end

        # Convert the provided object to a UTC time to store in the database.
        #
        # @example Set the time.
        #   Time.convert_to_time(Date.today)
        #
        # @param [ String, Date, DateTime, Array ] value The object to cast.
        #
        # @return [ Time ] The object as a UTC time.
        #
        # @since 1.0.0
        def convert_to_time(value)
          time = Mongoid::Config.use_activesupport_time_zone? ? ::Time.zone : ::Time
          case value
            when ::String
              time.parse(value)
            when ::DateTime
              return value if value.utc? && Mongoid.use_utc?
              time.local(value.year, value.month, value.day, value.hour, value.min, value.sec)
            when ::Date
              time.local(value.year, value.month, value.day)
            when ::Array
              time.local(*value)
            else
              value
          end
        end

        # Strip the milliseconds off the time.
        #
        # @todo Durran: Why is this here? Still need time refactoring.
        #
        # @example Strip.
        #   Time.strip_millseconds(Time.now)
        #
        # @param [ Time ] time The time to strip.
        #
        # @return [ Time ] The time without millis.
        #
        # @since 2.1.0
        def strip_milliseconds(time)
          ::Time.at(time.to_i)
        end
      end
    end
  end
end