module Sorcery
  module Model
    module Submodules
      # This submodule adds the ability to make the user activate his account via email
      # or any other way in which he can recieve an activation code.
      # with the activation code the user may activate his account.
      # When using this submodule, supplying a mailer is mandatory.
      module UserActivation
        def self.included(base)
          base.sorcery_config.class_eval do
            attr_accessor :activation_state_attribute_name,               # the attribute name to hold activation state
                                                                          # (active/pending).
                                                                          
                          :activation_token_attribute_name,               # the attribute name to hold activation code
                                                                          # (sent by email).
                                                                          
                          :activation_token_expires_at_attribute_name,    # the attribute name to hold activation code
                                                                          # expiration date. 
                                                                          
                          :activation_token_expiration_period,            # how many seconds before the activation code
                                                                          # expires. nil for never expires.
                                                                          
                          :user_activation_mailer,                        # your mailer class. Required.
                          :activation_needed_email_method_name,           # activation needed email method on your
                                                                          # mailer class.
                                                                          
                          :activation_success_email_method_name,          # activation success email method on your
                                                                          # mailer class.
                                                                          
                          :prevent_non_active_users_to_login              # do you want to prevent or allow users that
                                                                          # did not activate by email to login?
          end
          
          base.sorcery_config.instance_eval do
            @defaults.merge!(:@activation_state_attribute_name             => :activation_state,
                             :@activation_token_attribute_name             => :activation_token,
                             :@activation_token_expires_at_attribute_name  => :activation_token_expires_at,
                             :@activation_token_expiration_period          => nil,
                             :@user_activation_mailer                      => nil,
                             :@activation_needed_email_method_name         => :activation_needed_email,
                             :@activation_success_email_method_name        => :activation_success_email,
                             :@prevent_non_active_users_to_login           => true)
            reset!
          end
          
          base.class_eval do
            # don't setup activation if no password supplied - this user is created automatically
            before_create :setup_activation, :if => Proc.new { |user| user.send(sorcery_config.password_attribute_name).present? }
            # don't send activation needed email if no crypted password created - this user is external (OAuth etc.)
            after_create  :send_activation_needed_email!, :if => Proc.new { |user| !user.external?}
          end
          
          base.sorcery_config.after_config << :validate_mailer_defined
          base.sorcery_config.after_config << :define_user_activation_mongoid_fields if defined?(Mongoid) and base.ancestors.include?(Mongoid::Document)
          if defined?(MongoMapper) and base.ancestors.include?(MongoMapper::Document)
            base.sorcery_config.after_config << :define_user_activation_mongo_mapper_fields
          end
          base.sorcery_config.before_authenticate << :prevent_non_active_login
          
          base.extend(ClassMethods)
          base.send(:include, InstanceMethods)


        end
        
        module ClassMethods
          # Find user by token, also checks for expiration.
          # Returns the user if token found and is valid.
          def load_from_activation_token(token)
            token_attr_name = @sorcery_config.activation_token_attribute_name
            token_expiration_date_attr = @sorcery_config.activation_token_expires_at_attribute_name
            load_from_token(token, token_attr_name, token_expiration_date_attr)
          end
          
          protected
          
          # This submodule requires the developer to define his own mailer class to be used by it.
          def validate_mailer_defined
            msg = "To use user_activation submodule, you must define a mailer (config.user_activation_mailer = YourMailerClass)."
            raise ArgumentError, msg if @sorcery_config.user_activation_mailer == nil
          end

          def define_user_activation_mongoid_fields
            self.class_eval do
              field sorcery_config.activation_state_attribute_name,            :type => String
              field sorcery_config.activation_token_attribute_name,            :type => String
              field sorcery_config.activation_token_expires_at_attribute_name, :type => DateTime
            end
          end

          def define_user_activation_mongo_mapper_fields
            self.class_eval do
              key sorcery_config.activation_state_attribute_name, String
              key sorcery_config.activation_token_attribute_name, String
              # no DateTime in MM
              key sorcery_config.activation_token_expires_at_attribute_name, Time
            end
          end
        end
        
        module InstanceMethods
          # clears activation code, sets the user as 'active' and optionaly sends a success email.
          def activate!
            config = sorcery_config
            self.send(:"#{config.activation_token_attribute_name}=", nil)
            self.send(:"#{config.activation_state_attribute_name}=", "active")
            send_activation_success_email! unless self.external?
            save!(:validate => false) # don't run validations
          end
          
          protected

          def setup_activation
            config = sorcery_config
            generated_activation_token = TemporaryToken.generate_random_token
            self.send(:"#{config.activation_token_attribute_name}=", generated_activation_token)
            self.send(:"#{config.activation_state_attribute_name}=", "pending")
            self.send(:"#{config.activation_token_expires_at_attribute_name}=", Time.now.in_time_zone + config.activation_token_expiration_period) if config.activation_token_expiration_period
          end

          # called automatically after user initial creation.
          def send_activation_needed_email!
            generic_send_email(:activation_needed_email_method_name, :user_activation_mailer) unless sorcery_config.activation_needed_email_method_name.nil?
          end

          def send_activation_success_email!
            generic_send_email(:activation_success_email_method_name, :user_activation_mailer) unless sorcery_config.activation_success_email_method_name.nil?
          end
          
          def prevent_non_active_login
            config = sorcery_config
            config.prevent_non_active_users_to_login ? self.send(config.activation_state_attribute_name) == "active" : true
          end

        end
      end
    end
  end
end