class User < ActiveRecord::Base

  acts_as_followable

  disable_perishable_token_maintenance(true)

  CHAT_STATUSES = {
    :invisible => 0,
    :available => 1,
    :busy => 2
  }
  
  ACTIVE   = 1
  INACTIVE = 0

  ACTIONS =  {
    :registered  => 1,
    :approved    => 2,
    :rejected    => 3,
    :activated   => 4,
    :suspended   => 5,
    :reactivated => 6,
    :archived    => 7
  }

  DISABLED_NOTIFICATIONS_FOR_ADMIN = []
  DISABLED_NOTIFICATIONS_FOR_CONTENT_CREATOR = ["blog_published_notification"]
  DISABLED_NOTIFICATIONS_FOR_USERS = ["blog_published_notification"]


    
  attr_accessor :force_password_validation, :current_password, :changing_password, :skip_user_limit_check, :skip_making_activation_code

  # override activerecord's find to allow us to find by name or id transparently
  # def self.find(*args)
  # super
  # end

  has_friendly_id :display_name, :use_slug => true, :approximate_ascii => true, :max_length => 100

  # Associations
  belongs_to :company
  belongs_to :role
  has_many :polls, :through => :posts
  has_many :contents
  has_many :events
  has_many :courses
  has_one  :merchant_account,  :as => :owner
  has_many :payments_received, :as => :payee, :class_name => "Payment"
  has_many :payments_made, :class_name => "Payment", :foreign_key => 'payer_id'
  has_many :questions
  has_many :questionnaires
  has_many :response_sets
  has_many :responses, :through => :response_sets
  has_many :documents, :as => :attachable, :dependent => :destroy, :class_name => "Document"
  has_many :scorm_packages, :as => :attachable, :dependent => :destroy, :class_name => "ScormPackage"
  has_many :tincan_packages, :as => :attachable, :dependent => :destroy, :class_name => "TincanPackage"

  has_many :drop_box_associations, :as => :associate
  has_many :drop_box_items_shared_with_me, :through => :drop_box_associations, :class_name => "DropBoxItem"

  has_many :drop_box_items_created_by_me, :as => :attachable, :dependent => :destroy, :class_name => "DropBoxItem"
  has_many :drop_box_items, :as => :attachable, :class_name => "DropBoxItem", :include => :drop_box_associations,  :conditions => ['(( drop_box_associations.associate_id= #{id} and drop_box_associations.associate_type =\'User\') or (assets.attachable_type = \'User\' and assets.attachable_id = #{id}))']

  has_many :owned_groups, :through => :memberships , :source => :group , :conditions => ['is_owner = ?', true], :foreign_key => 'user_id'
  has_many :meetings, :class_name => "Meeting", :foreign_key => 'owner_id'
  has_many :enrollments
  has_many :created_enrollments, :class_name => 'Enrollment', :foreign_key => 'enrolled_by_id'
  has_many :completed_enrollments, :class_name => 'Enrollment', :conditions => ['completed_at is not null']
  has_many :enrolled_courses, :source_type => 'Course', :through => :enrollments, :source => :enrollable
  has_many :enrolled_live_events, :source_type => 'LiveEvent', :through => :enrollments, :source => :enrollable
  has_many :enrolled_certifications, :source_type => 'Certification', :through => :enrollments, :source => :enrollable
  has_many :enrolled_bundles, :source_type => 'Bundle', :through => :enrollments, :source => :enrollable
  has_many :completed_certifications, :source_type => 'Certification', :through => :completed_enrollments, :order => "completed_at DESC", :source => :enrollable
  has_many :enrolled_in_person_events, :source_type => 'InPersonEvent', :through => :enrollments, :source => :enrollable
  has_many :completed_courses, :source_type => 'Course', :through => :completed_enrollments, :order => "completed_at DESC", :source => :enrollable

  has_many :selected_certifications, :source_type => 'Certification', :through => :selections, :source => :selectable
  has_many :selected_certificates, :source_type => 'Certificate', :through => :selections, :source => :selectable
  has_many :selected_events, :source_type => 'Event', :through => :selections, :source => :selectable
  has_many :likings, :class_name => 'Like', :foreign_key => 'user_id'
  has_many :follows
  #list users to whom I am following
  has_many :following_users, :through => :follows, :source => :followable, :source_type => 'User'
  has_many :follower_users, :through => :followers, :source => :user

  has_many :recommendations_given, :class_name => 'Recommendation', :foreign_key => 'by_user_id'
  has_many :recommendation_users, :foreign_key => 'to_user_id'
  has_many :recommendations, :through => :recommendation_users
  has_many :courses_shared_by_me, :through => :recommendations_given, :source => :recommendable, :source_type => 'Course'

  has_many :bookmarks
  has_many :bookmarked_courses, :through => :bookmarks, :source => :bookmarkable, :source_type => 'Course'

  has_many :removed_announcements, :class_name => 'UserRemovedAnnouncement', :foreign_key => 'user_id'
  has_many :marked_as_viewed_annoucements, :class_name => "Announcement", :through => :removed_announcements, :source => :announcement

  has_many :activities, :foreign_key => "user_id", :order => "created_at desc", :dependent => :destroy
  has_many :owned_contents, :class_name => 'Content'
  has_many :content_availabilities
  has_many :available_contents, :through => :content_availabilities, :class_name => 'Content', :source => :content
  has_many :orders
  has_many :selections, :dependent => :destroy
  #has_many :selected_events, :class_name => "Event",
  #        :finder_sql => 'SELECT * from events e,selections s WHERE s.user_id = #{id} AND e.id IN (s.selectable_ids)'

  has_many :certifications
  has_many :live_events
  has_many :products, :foreign_key => "owner_id"
  has_one :subscription, :class_name => 'UserSubscription'
  has_one :billing_address
  has_many :in_person_events
  has_many :bundles

  has_many :memberships, :class_name => 'GroupMembership'
  has_many :groups, :through => :memberships, :conditions => 'accepted_at IS NOT NULL'
  has_many :pending_groups, :through => :memberships, :source => :group, :conditions => 'accepted_at IS NULL'
  has_many :declined_groups, :through => :memberships, :source => :group, :conditions => 'declined_at IS NOT NULL'

  has_one :disk_usage
  has_many :web_links
  has_many :web_texts

  has_many :client_applications
  has_many :tokens, :class_name => "OauthToken", :order => "authorized_at desc", :include => [:client_application]
  has_many :product_questions
  has_many :ratings_created, :class_name => 'Rating'
  has_many :group_topics
  has_many :group_topic_replies, :through => :group_topics, :class_name => 'Comment', :source => :replies
  has_many :panel_positions
  has_many :folders

  # returns only active certificates
  has_many :certificates, :through => :enrollments, :source => :active_certificate

  # returns all certificates including inactive certificates
  has_many :all_certificates, :through => :enrollments, :source => :certificates
  
  has_many :site_subscriptions_canceled_by_me, :class_name => 'SiteSubscription', :foreign_key => 'canceled_by_user_id'
  
  has_many :user_notification_settings, :dependent => :destroy
  
  has_one :google_oauth_token, :dependent => :destroy
  has_one :google_contact, :dependent => :destroy
  has_many :product_permissions
  has_many :shared_courses, :through => :product_permissions, :source => :associated, :source_type => 'Course'

  # Validations
  validates_presence_of :company_id, :firstname, :lastname, :email
  validate_on_create :validate_email_with_company_domains
  validates_acceptance_of :terms_of_service, :accept => "1"
  validates_format_of :firstname, :with => /^[^'"]*$/, :allow_nil => true, :message => :must_not_contain_quotes.l
  validates_format_of :lastname, :with => /^[^'"]*$/, :allow_nil => true, :message => :must_not_contain_quotes.l
  validate :validate_uniquness_of_email_case_insenitive
  validate :validate_old_password
  validate :validate_email_with_blacklisted_domains

  acts_as_authentic do |c|

    c.logged_in_timeout = 60.minutes # default is 10.minutes
    c.crypto_provider = CommunityEngineSha1CryptoMethod

    c.login_field = :email

    c.validates_uniqueness_of_login_field_options= {:scope => :company_id}
    c.validates_uniqueness_of_email_field_options= {:scope => :company_id}

    c.validates_length_of_password_field_options = { :within => 6..20, :if => :password_required? }
    c.validates_length_of_password_confirmation_field_options = { :within => 6..20, :if => :password_required? }

    c.validates_length_of_email_field_options = { :within => 3..100 }
    c.validates_format_of_email_field_options = { :with => /^([^@\s]+)@((?:[-a-z0-9A-Z]+\.)+[a-zA-Z]{2,})$/ }
  end

  #solr
  searchable do
    text :firstname
    text :lastname
    text :email
    integer :company_id
  end

  # CallBacks
  before_create :assign_default_role
  before_create :make_activation_code, :if => Proc.new { |record | !record.skip_making_activation_code }
  before_create :set_status
  before_create :assign_disk_usage
  before_create :set_activation_attributes_if_required
  before_save :update_strings_case, :set_unsubscribe_token
  after_update :deliver_approve_reject_notification, :verify_company_user_limit_on_update
  after_update :enroll_to_intro_courses, :if => Proc.new { |record | record.role_id_changed? }
  after_validation :check_errors_on_password
  before_create :set_guid

  # Named scopes
  named_scope :to_approve, :conditions => ["users.status = 0  and users.action = #{ACTIONS[:registered]}"]

  named_scope :suspended_or_rejected, :conditions => ["status = 0 AND (users.action = #{ACTIONS[:suspended]} OR users.action = #{ACTIONS[:rejected]})"]
  named_scope :currently_online, lambda {{:conditions => ["sb_last_seen_at > ?", Time.zone.now.utc - 2.minutes] }}
  named_scope :online_last_24_hrs, lambda {{:conditions => ["sb_last_seen_at > ?", Time.zone.now.utc - 24.hours] }}

  named_scope :active, :conditions => ["status = 1"]
  named_scope :subscription_active, :conditions => {:subscription_active => true }
  named_scope :not_invisible, :conditions => ["chat_status != ?",CHAT_STATUSES[:invisible]]
  named_scope :search_email_name, lambda { |search_str|
    {
      :conditions => ["email like :str or firstname like :str or lastname like :str", {:str => "%#{search_str}%"} ]
    }

  }
  
  named_scope :filter_by, lambda { |condition, value|
    { 
      :conditions => {condition.to_sym => value}
    }
  }

  named_scope :most_active, order_by("login_count desc")
  named_scope :instructors, :include => :role, :conditions => ["roles.name IN(?)", [Role::DEFAULTS[:instructor], Role::DEFAULTS[:admin], Role::DEFAULTS[:destroyer], Role::DEFAULTS[:super_admin]]]
  named_scope :order_by_firstname, lambda{ { :order => "firstname"} }
  named_scope :order_by_lastname, lambda{ { :order => "lastname"} }
  named_scope :activated, :conditions => ["status = 1 AND activated_at IS NOT NULL"]
  named_scope :except_admin, :include => :role, :conditions => ["roles.name != ?", Role::DEFAULTS[:admin]]
  named_scope :admins, :include => :role, :conditions => ["roles.name IN(?)", [Role::DEFAULTS[:admin], Role::DEFAULTS[:super_admin]]]
  named_scope :except, lambda { |user_ids|
    ids = [user_ids].flatten
    {
        :conditions => ["users.id NOT IN(?)", ids]
    }
  }

  def params_for_url_path(use_subdomain = false, use_current_host = false)
    self.company.params_for_url_path(use_subdomain, use_current_host)
  end

  def self.authenticate_safely(user_name, password)
    find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
  end

  def subdomain_for_url_path(use_subdomain = false)
    self.company.subdomain_for_url_path(use_subdomain)
  end

  def destroyer?
    role && (role.eql?(self.company.roles.destroyer.first) || self.super_admin?)
  end
  
  def subscription_active? 
    self.subscription_active || self.site_owner?
  end
  
  def site_owner?
    self.id == self.company.admins.first.id
  end
  
  def super_admin?
    role && role.eql?(self.company.roles.super_admin.first)
  end

  def admin?
    role && (role.eql?(self.company.roles.admin.first) || self.destroyer? || self.super_admin?)
  end

  def moderator?
    role && role.eql?(self.company.roles.moderator.first)
  end

  def instructor?
    role && role.eql?(self.company.roles.instructor.first)
  end

  def member?
    role && role.eql?(self.company.roles.member.first)
  end

  def active?
    if self.company.user_activation_required
      self.status == ACTIVE && activation_code.nil? && !activated_at.nil?
    else
      #Either approved or activated
      self.status == ACTIVE && (self.action == ACTIONS[:approved] || self.action == ACTIONS[:activated] || self.action == ACTIONS[:reactivated])
    end
  end

  def super_site_admin?
    self == User.site_admin
  end

  def self.site_admin
    User.first :conditions => {:site_admin => true}
  end

  def self.current_host
    Thread.current[:host]
  end

  def self.current_host=(host)
    Thread.current[:host] = host
  end

  def self.sign_up_between(company_id, start_date, end_date)
    User.all(
      :select => "date(created_at) each_date, UNIX_TIMESTAMP(date(created_at))  epoch, year(created_at) year, month(created_at) month, dayofmonth(created_at) day, count(created_at) user_count",
      :group => "date(created_at)" ,
      :conditions => ["company_id = ? and created_at between ? and ?", company_id, start_date, end_date]
    )
  end

  def self.total_users_between(company_id, start_date, end_date)
    count = User.count(:conditions => ["company_id = ? and date(created_at) <= ?", company_id, start_date - 1.day])
    stats = sign_up_between(company_id, start_date, end_date)
    cumulative_count = count
    end_date = end_date - 1.day
    start_record_found = end_record_found = false
    stats.each do |user|
      user.user_count = user.user_count.to_i + cumulative_count
      cumulative_count = user.user_count
      start_record_found = true if (start_date.year == user.year.to_i && start_date.month == user.month.to_i && start_date.day == user.day.to_i )
      end_record_found = true if (end_date.year == user.year.to_i && end_date.month == user.month.to_i && end_date.day == user.day.to_i )
    end
    return [] if stats.blank?
    unless start_record_found
      record = stats.first.clone
      record.each_date = start_date
      record.year = start_date.year
      record.month = start_date.month
      record.day = start_date.day
      record.user_count = count
      stats.unshift(record)
    end
    unless end_record_found
      record = stats.first.clone
      record.each_date = end_date
      record.year = end_date.year
      record.month = end_date.month
      record.day = end_date.day
      record.user_count = cumulative_count
      stats << record
    end

    stats
  end

  def self.recent_activity(company_id, page = {}, options = {})
    page.reverse_merge! :size => 10, :current => 1
    Activity.recent.find(:all,
      :select => 'activities.*',
      :conditions => ["users.activated_at IS NOT NULL AND activities.company_id = ?", company_id],
      :joins => "LEFT JOIN users ON users.id = activities.user_id",
      :page => page, *options)
  end

  def self.find_by_activity(company_id, options = {})
    options.reverse_merge! :limit => 30, :require_avatar => true, :since => 7.days.ago

    activities = Activity.since(options[:since]).find(:all,
      :select => 'activities.user_id, count(*) as count',
      :group => 'activities.user_id',
      :conditions => ["#{options[:require_avatar] ? ' users.avatar_id IS NOT NULL AND ' : ''} users.activated_at IS NOT NULL AND users.company_id = ?", company_id],
      :order => 'count DESC',
      :joins => "LEFT JOIN users ON users.id = activities.user_id",
      :limit => options[:limit]
    )
    activities.map{|a| find(a.user_id) }
  end



  # def to_param
  # self.id.to_s
  # end
  #
  def chat_status
    return CHAT_STATUSES[:invisible] unless self.online?
    read_attribute(:chat_status)
  end

  def to_json_for_chat
    for_chat.to_json
  end

  def for_chat
    random = Time.now.hash.abs
    {
      :id => self.id,
      :name => self.display_name,
      :image => self.avatar_photo_url(:thumb),
      :status => self.chat_status,
      :type => 'User',
      :company_id => self.company_id,
      :groups => online_groups
    }
  end

  def chat_list_json
    chat_hash = {:from => self.for_chat}
    online_users = Expertus::OnlineUserStore.instance.online_users(self.company_id)
    users = []
    online_users.each do |user|
      if user['id'] != self.id
        users << user
      end
    end
    #online_users.each do |user|
    #  follow = user.followed_by?(self)
    #  users << user.for_chat.merge(:following => follow ? follow.id : false)
    #end
    chat_hash[:to] = users

    chat_hash[:groups] = online_groups
    chat_hash.to_json
  end

  def online_groups
    user_groups = self.groups
    groups_json = []

    user_groups.collect do |group|
      group.for_chat
    end
  end

  def set_activation_attributes_if_required
    if !self.company.user_activation_required && (self.company.user_registration_type == Company::REGISTRATION_TYPE[:self] || self.created_by.present?)
      self.activated_at     = Time.zone.now.utc
      self.activation_code  = nil
      self.action           = ACTIONS[:activated]
      @activated            = true
    end
  end

  def activate
    if self.company.admins.size == 1 && self.company.admins.first.id == self.id
      # Company activated for first time.
      self.company.activate!
      GA_EVENT_RECORDER.new_company_verification(Thread.current[:host])
    else
      @activated = true
    end
    update_attributes(:activated_at => Time.zone.now.utc, :activation_code => nil, :action => ACTIONS[:activated])
  end
  
  def archive
    update_attributes(:status => INACTIVE, :action => ACTIONS[:archived] )
  end
  
  def archived?
    self.status == INACTIVE && self.action == ACTIONS[:archived]
  end

  def admin_approve
    return if self.action == ACTIONS[:approved] && self.status == ACTIVE
    @approved = true
    # if the user was created from SF then directly activate the user after admin approves the user
    if self.created_from_salesforce? || !self.company.user_activation_required
      update_attributes(:status => ACTIVE, :action => ACTIONS[:approved])
      self.activate
    else
      self.make_activation_code
      update_attributes(:status => ACTIVE, :action => ACTIONS[:approved])
    end
  end

  def admin_approved?
    @approved
  end

  def being_suspended?
    @suspended
  end

  def being_reactivated?
    @reactivated
  end

  def reject
    return if self.action == ACTIONS[:rejected] && self.status == INACTIVE
    @rejected = true
    update_attributes(:status => INACTIVE, :action => ACTIONS[:rejected] , :activation_code => nil, :activated_at => nil )
  end

  def rejected?
    @rejected
  end

  def suspend
    return if self.action == ACTIONS[:suspended] && self.status == INACTIVE
    @suspended = true
    update_attributes(:status => INACTIVE, :action => ACTIONS[:suspended] )
  end

  def reactivate
    return if self.action == ACTIONS[:reactivated] && self.status == ACTIVE
    attribtues_to_update = {:status => ACTIVE, :action => ACTIONS[:activated]}
    if activated_at.nil? && self.company.user_activation_required?
      if self.activation_code.nil?
        activation_token = generate_activation_token
        self.activation_code = activation_token
        attribtues_to_update.merge!(:activation_code => activation_token)
      end
      UserNotifier.deliver_signup_notification(self)
    else
      attribtues_to_update.merge!(:activated_at => Time.zone.now) if self.activated_at.blank?
      UserNotifier.deliver_activation(self, true)
    end
    @reactivated = true
    update_attributes(attribtues_to_update.merge!(:ownership_given_to => nil, :ownership_changed_at => nil))
  end

  def online?
    # online_users = Expertus::OnlineUserStore.instance.online_users(self.company_id)
    # online_users.detect do |u|
    #   u['id'] == self.id
    # end.present?
    true
  end

  def full_name
    "#{self.firstname} #{self.lastname}".strip
  end

  # This is used in the UI.
  # For user with first and last name Joe and Welsh, it returns "Joe W"
  def display_name
    if self.firstname && self.lastname
      "#{self.firstname.string_titleize} #{self.lastname[0,1].capitalize}".strip
    else
      "default"
    end
  end

  def chat_status_text
    case self.chat_status
    when CHAT_STATUSES[:available]
      :available.l
    when CHAT_STATUSES[:busy]
      :busy.l
    when CHAT_STATUSES[:invisible]
      :invisible.l
    end
  end

  def avatar_photo_url(size = nil)
    if avatar && !hide_profile_photo
      avatar.public_filename(size)
    else
      case size
      when :thumb
        AppConfig.photo['missing_thumb']
      when :medium
        AppConfig.photo['missing_medium']
      when :large
        AppConfig.photo['missing_large']
      else
        AppConfig.photo['missing_medium']
      end
    end
  end

  def enroll_to(item_ids, item_type, skip_count_update = false, enrolled_by = nil, skip_display_check = false)
    Rails.logger.info "[[User::enroll_to]] item_ids: #{item_ids.inspect}, item_type: #{item_type.inspect}, skip_count_update: #{skip_count_update.inspect}, enrolled_by: #{enrolled_by.inspect}, skip_display_check: #{skip_display_check.inspect}"
    enrolled_ids = []
    already_enrolled_items = self.send("enrolled_#{item_type.underscore.pluralize}")
    Rails.logger.info "[[User::enroll_to]] Already enrolled items: #{already_enrolled_items.inspect}"
    item_ids.each do |item_id|
      item = item_type.constantize.find_by_id(item_id)
      already_enrolled = already_enrolled_items.include?(item)
      canceled_enrollment = self.enrollments.canceled.all(:conditions => ["enrollable_type = ? AND enrollable_id  = ?", item_type, item_id]).first if already_enrolled
      Rails.logger.info "[[User::enroll_to]] item: #{item.inspect}, canceled_enrollment: #{canceled_enrollment.inspect}, being_displayed_in_catalog: #{item.present? ? item.being_displayed_in_catalog?.inspect : ''}, item_available_for_user_groups: #{item.present? ? item.available_for_user_groups?(self).inspect : ''}"
      if item && (item.being_displayed_in_catalog? || skip_display_check) && item.available_for_user_groups?(self) && (!already_enrolled || canceled_enrollment.present?)
        item.update_enrollment_count unless skip_count_update
        enrolled_ids << item.id
        unless already_enrolled
          if item_type == 'Bundle'
            BundleEnrollment::Enroller.new(item.id, self.id).enroll
          else
            self.send("enrolled_#{item_type.underscore.pluralize}") << item
          end
          item.after_enrolled_to(self.id)
          self.enrollments.first(:conditions => ["enrollable_type = ? AND enrollable_id  = ?", item_type, item_id]).update_attributes(:enrolled_by_id => enrolled_by) if enrolled_by
        else
          # If user was already enrolled but had canceled the enrollment then reactivate the enrollment
          unless canceled_enrollment.blank?
            canceled_enrollment.reactivate!

            # If a canceled item is being reactivated here, it means that user would have reenrolled to it
            # by directly clicking on "add to learning" in catalog page and not via a bundle. Hence clear out
            # the bundle details.
            canceled_enrollment.update_attributes(:bundle_id => nil, :is_available => nil, :enrolled_by_id => enrolled_by)
          end
        end
      end
    end
    Rails.logger.info "[[User::enroll_to]] enrolled_ids: #{enrolled_ids.inspect}"
    enrolled_ids
  end

  def generate_login_slug
    self.login_slug = self.display_name.parameterize
  end

  def score_for(certification)
    enrollment = self.enrollments.active.of('Certification').find_by_enrollable_id(certification.id)

    if enrollment
      enrollment.score_in_percentage
    else
      response_set = self.response_sets.for_associated(certification).find(:first, :order => 'created_at DESC')
      response_set ? response_set.score_in_percent : 0
    end
  end

  def passed_certifications(only_selected= false, only_count=false, limit=nil)
    passed_ids = self.response_sets.passed_certifications.map(&:associated_id)
    order = nil
    if only_selected
      selected_ids = self.selections.of('Certification').map(&:selectable_id )
      passed_ids = selected_ids & passed_ids
      order = "FIND_IN_SET(certifications.id, '#{passed_ids.join(',')}')"
    end
    if only_count
      self.enrolled_certifications.count(:conditions => {:id => passed_ids})
    else
      self.enrolled_certifications.find(:all, :conditions => {:id => passed_ids}, :order => order, :limit => limit)
    end
  end

  def deliver_activation
    if self.recently_activated?
      UserNotifier.deliver_activation(self)
      @activated = false
    end
  end

  def upcoming_items(type = 'all')
    upcoming_items = []
    upcoming_items = if type == 'all'
      self.enrollments.active.recently_accessed.not_archived.incomplete.available.except_of('Bundle').all.map(&:enrollable)
    else
      type == 'Event' ? type = ['InPersonEvent', 'LiveEvent'] : [type]
      self.enrollments.of(type).active.recently_accessed.not_archived.incomplete.available.except_of('Bundle').all.map(&:enrollable)
    end
    upcoming_items.reject! do |item|
      (self.company.new_user_videos_disabled? && item.system_generated) || (item.instance_of?(LiveEvent) && item.expired?) || (item.instance_of?(InPersonEvent) && item.expired?)
    end
    upcoming_items
  end

  def deliver_password_reset_instructions!
    reset_perishable_token!
    UserNotifier.deliver_password_reset_instructions(self)
  end

  def deliver_password_reset_confirmation!
    reset_perishable_token!
    UserNotifier.deliver_password_reset_confirmation(self)
  end

  def deliver_member_password_reset_by_admin!
    reset_perishable_token!
    UserNotifier.deliver_member_password_reset_by_admin(self)
  end  

  def has_valid_subscription?
    self.subscription.try(:is_valid?)
  end

  def belongs_to?(company)
    if company.instance_of?(Fixnum)
      self.company_id == company
    else
      self.company == company
    end
  end

  # use group object to find the membership associated. Use mainly in other methods.
  def membership(group)
    GroupMembership.find(:last, :conditions => ['user_id = ? AND group_id = ?', self.id, group.id])
  end

  def is_member_of?(group)
    self.groups.include?(group)
  end

  def request_membership_of(group)
    group.members << self unless self.is_member_of?(group)
  end

  def pending_and_accepted_groups
    self.pending_groups + self.groups
  end

  def all_groups
    (self.pending_groups + self.groups + self.declined_groups).flatten
  end

  def leave(group)
    membership = self.membership(group)
    if membership 
      membership.update_attributes(:is_owner => false, :declined_at => nil)
      membership.destroy
    end
  end

  def become_member_of(group)
    group.members << self unless self.all_groups.include?(group)
    group.accept_member(self)
  end

  def generate_activation_token
    Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
  end
  
  def generate_google_sso_code
    generate_activation_token
  end

  def self.action_key_for(value)
    key = ACTIONS.select {|k, v| v == value}.flatten
    key = key.first
  end

  def self.action_text_for(value)
    self.action_key_for(value).to_s
  end

  def self.auto_suggest_xml(users)
    xml =  Builder::XmlMarkup.new(:indent => 2).instruct!
    xml << "<users type='Array'><count>#{users.size}</count>"
    users.each do |user|
      xml << "<user><email>#{user.email}</email>"
      xml << "<displayname>#{user.display_name}</displayname></user>"
    end
    xml << "</users>"
  end

  def truncated_email
    self.email.downcase.pretty_truncate(18)
  end

  def new_drop_box_items_count
    self.drop_box_associations.associated_users.not_viewed.count
  end

  def active_announcements(audience = nil)
    audience ||= self.company
    announcements = self.admin? ? audience.announcements.for_user(self).recent.active : audience.announcements.exclude_admin_types.for_user(self).recent.active
    announcements - self.marked_as_viewed_annoucements
  end

  def mark_user_subscription_active
    self.update_attribute(:subscription_active, true)
  end

  # includes products and enrollments
  def archived_items
    (self.enrollments.archived + self.products.archived).sort { |a, b| b.archived_at <=> a.archived_at }
  end

  def achievements
    certifications_with_badge = self.completed_certifications.select {|c| c.badge_type.present? && c.passed_by?(self) }
    self.certificates + certifications_with_badge
  end

  def selected_achievements
    self.selected_certificates + self.selected_certifications
  end
  
  def can_receive?(notification)
    user_notification_setting = self.user_notification_settings.first(:conditions => ["notification = ?", notification])
    user_notification_setting.blank? ? self.check_default_notification_settings(notification) : user_notification_setting.enabled
  end

  def check_default_notification_settings(notification)
    if self.admin?
      !DISABLED_NOTIFICATIONS_FOR_ADMIN.include?(notification)
    elsif self.instructor? || self.moderator?
      !DISABLED_NOTIFICATIONS_FOR_CONTENT_CREATOR.include?(notification)
    elsif self.member?
      !DISABLED_NOTIFICATIONS_FOR_USERS.include?(notification)
    end
  end
  
  def courses_seen_in_catalog(except_course = nil)
    exclude_options = except_course.present? ? [except_course.product] : []
    products = Product.search(self.company, self, :associated_type => 'Course', :exclude_options => exclude_options, :per_page => 10000).results
    list = products.collect(&:associated)
    list
  end
  
  def self.create_from_google_oauth(company, info = {}, uid = nil)
    user = company.users.find_by_email(info['email'])
    return user if user
    user = User.new(:firstname => info['first_name'], :lastname => info['last_name'], :email => info['email'], :google_uid => uid)
    user.password = user.password_confirmation = Authlogic::Random.friendly_token # assign a random password
    user.birthday = 13.years.ago.to_date
    user.reset_perishable_token
    user.reset_single_access_token
    user.company_id = company.id

    User.skip_callback(:deliver_signup_notification) do
      if user.save
        if company.user_registration_type == Company::REGISTRATION_TYPE[:self]
          user.activate
        end
      end
    end
    user
  end
  
  def required_and_recommended_courses
    completed_enrollments = self.enrollments.of('Course').completed.reject do |e|
      !e.enrollable.has_quiz? || !e.enrollable.suggestions.count == 0
    end

    suggestion_items = completed_enrollments.collect do |e|
      course = e.enrollable
      percentage_score = e.quiz_score
      course.suggestions.greater_then_threshold(percentage_score)
    end.flatten

    uniq_suggestion_items, req_items, rec_items, req_sugg_items, rec_sugg_items = [], [], [], [], []

    suggestion_items.each do |product_suggestion|
      if product_suggestion.suggestion_type == ProductSuggestion::SUGGESTION_TYPES[:required]
        req_items << product_suggestion unless req_sugg_items.include? product_suggestion.suggestion_item
        req_sugg_items << product_suggestion.suggestion_item
      else
        rec_items << product_suggestion unless rec_sugg_items.include? product_suggestion.suggestion_item
        rec_sugg_items << product_suggestion.suggestion_item
      end
    end
    
    uniq_suggestion_items = req_items + rec_items

    uniq_suggestion_items
  end

  def recommended_courses(options = {})
    suggestion_items = options[:suggestion_items] || self.required_and_recommended_courses
    include_enrolled = options[:include_enrolled].nil? ? true : options[:include_enrolled]

    suggestion_items.select do |item|
      if include_enrolled
        item.suggestion_type == ProductSuggestion::SUGGESTION_TYPES[:recommended]
      else
        enrolled_item = self.enrollments.active.of('Course').find_by_enrollable_id(item.suggestion_item_id)
        item.suggestion_type == ProductSuggestion::SUGGESTION_TYPES[:recommended] && enrolled_item.blank?
      end
    end

  end

  def required_courses(options = {})
    suggestion_items = options[:suggestion_items] || self.required_and_recommended_courses
    include_enrolled = options[:include_enrolled].nil? ? true : options[:include_enrolled]

    suggestion_items.select do |item|
      if include_enrolled
        item.suggestion_type == ProductSuggestion::SUGGESTION_TYPES[:required]
      else
        enrolled_item = self.enrollments.active.of('Course').find_by_enrollable_id(item.suggestion_item_id)
        item.suggestion_type == ProductSuggestion::SUGGESTION_TYPES[:required] && enrolled_item.blank?
      end
    end
  end
  
  def search_google_contacts(search_text)
    return [] if self.google_contact.blank? || self.google_contact.contact.blank?
    contacts = self.google_contact.contact
    contacts.select {|con| (con[:email] =~ /^#{search_text}/i) || (con[:full_name] =~ /^#{search_text}/i)}
  end
    
  def update_google_contacts
    oauth_token = self.google_oauth_token
    if oauth_token && oauth_token.expires_at.present? && oauth_token.expires_at > Time.now.to_i
      ac_token = OAuth2::AccessToken.new(OAuth2::Client.new(GOOGLE_SSO_CONFIG['client_id'], GOOGLE_SSO_CONFIG['client_secret']), oauth_token.access_token)
      raw_contacts = GoogleContactsApi::User.new(ac_token).contacts
      if raw_contacts.present?
        cons = raw_contacts.collect do |con|
          next if con.emails.blank?
          names = con.full_name ? con.full_name.split : []
          display_name = "#{names[0].string_titleize} #{names.last == names.first ? '' : names.last[0,1]}".strip if names.length > 0
          
          {:emails => con.emails, :full_name => con.full_name, :given_name => con.given_name, :display_name => display_name}
        end.compact
        
        self.google_contact ? self.google_contact.update_attributes({:contact => cons}) : GoogleContact.create({:contact => cons, :user_id => self.id})
      end
    end
  rescue GoogleContactsApi::UnauthorizedError, GoogleContactsApi::ApiError, StandardError
  end

  def enroll_to_intro_courses
    intro_courses = []
    courses_to_enroll_into = []
    if self.role
      case self.role.name
        when 'admin', 'super_admin'
          intro_courses = self.company.courses(true).system_generated.introduction_admin
          intro_courses << self.company.courses(true).system_generated.introduction_instructors
          intro_courses << self.company.courses(true).system_generated.introduction_learners
        when 'instructor'
          intro_courses = self.company.courses(true).system_generated.introduction_instructors
          intro_courses << self.company.courses(true).system_generated.introduction_learners
        when 'member'
          intro_courses = self.company.courses(true).system_generated.introduction_learners
      end
      intro_courses.flatten.each do |course|
        already_enrolled = self.enrolled_courses.include?(course)
        if !already_enrolled
          courses_to_enroll_into << course
        end
      end
      self.enroll_to(courses_to_enroll_into.collect(&:id), 'Course', true, nil, true)
    end
  end

  def deliver_quiz_with_essay_completed_notification(item, learner)
    if self.can_receive?('product_with_essay_to_be_graded_completed_notification')
      UserNotifier.deliver_notify_quiz_with_essay_completed(item, learner)
    end
  end

  def set_unsubscribe_token
    if self.unsubscribe_token.blank?
      self.unsubscribe_token = SecureRandom.hex
    end
  end

  def unsubscribe_from_all_notification
    applicable_notifications = self.applicable_notifications

    self.transaction do
      applicable_notifications.each do |notification_setting|
        user_notification_setting = self.user_notification_settings.first(:conditions => ["notification_id = ?", notification_setting.id])

        if user_notification_setting
          user_notification_setting.disable!
        else
          self.user_notification_settings.create!(:notification => notification_setting.notification, :notification_id => notification_setting.id, :enabled => false)
        end
      end
    end
  end

  def applicable_notifications
    NotificationSetting.applicable_settings_for(self)
  end
  
  def has_valid_membership?
    self.company.show_member_pricing_for_items && self.has_membership?
  end
  
  def change_role(role)
    if role.name == Role::DEFAULTS[:super_admin] && !self.super_admin?
      current_super_admin = self.company.super_admin
      current_super_admin.update_attribute('role_id', self.company.roles.admin.first.id) if current_super_admin
    end
    self.update_attribute('role_id', role.id)
  end

  private
 
  def set_status
    self.status =  if self.company.user_registration_type == Company::REGISTRATION_TYPE[:self]
      ACTIVE
    else
      INACTIVE
    end
    self.action = self.company.user_registration_type == Company::REGISTRATION_TYPE[:self] ? ACTIONS[:approved] : ACTIONS[:registered]
  end

  def assign_default_role
    return if !self.role.blank?
    if Settings.website_mode == WEBSITE_MODE_USER
      self.role = company.roles.instructor.first
    else
      self.role = company.default_role
    end
  end

  def assign_disk_usage
    self.build_disk_usage(:size => 0)
  end

  def validate_email_with_company_domains
    return if self.company_id.blank? || self.email.blank?
    return if self.admin?
    email_domain = self.email.split('@')[1]
    if self.company(true).name.downcase != AppConfig.default_company['name'].downcase
      unless self.company.email_domains.active.blank?
        self.errors.add(:email, :email_is_not_approved_for_site.l(:domain => email_domain)) if !self.company.email_domains.active.collect(&:domain).include?(email_domain)
      end
    end
  end

  def validate_email_with_blacklisted_domains
    return if self.company_id.blank? || self.email.blank?
    email_domain = self.email.split('@')[1]

    is_blacklisted = AppConfig.blacklisted_email_domains.any? do |blacklist|
      email_domain && email_domain.include?(blacklist)
    end

    if is_blacklisted
      self.errors.add(:email, :email_is_not_allowed.l)
    end
  end

  def deliver_approve_reject_notification
    if self.rejected?
      UserNotifier.deliver_rejected(self)
    elsif self.admin_approved?
      if self.created_from_salesforce?
        UserNotifier.deliver_sso_signup_notification_after_approval(self)
      elsif !self.company.user_activation_required?
        # Nothing to do
      else
        UserNotifier.deliver_approved(self)
      end
    end
  end

  def deliver_signup_notification
    #send signup notification message only if the company is active
    if self.company.active?
      if self.created_by.blank? #created by self
        if self.company.user_registration_type == Company::REGISTRATION_TYPE[:self]
          # users created from SF does not require activate email rather send them other email
          if self.created_from_salesforce?
            UserNotifier.deliver_sso_signup_notification(self)
          elsif !self.company.user_activation_required
            deliver_activation
          else
            UserNotifier.deliver_signup_notification(self)
          end
        else
          UserNotifier.deliver_signup_notification_admin_approved(self)
          UserNotifier.deliver_sigup_notification_to_company_admin(self) if self.company.user_registration_type == Company::REGISTRATION_TYPE[:admin_approved_with_email_alert]
        end
      else  # created by admin of the company
        if self.reload.active?
          UserNotifier.deliver_signup_notification_admin_created_and_activated(self)
        else
          UserNotifier.deliver_signup_notification_admin_created(self)
        end
      end
    end
  end

  def self.search_by_display_name(company, search_text)
    solr_search do
      keywords search_text
      with(:company_id, company.id)
    end
  end
  
  def validate_uniquness_of_email_case_insenitive
    conditions = {:email => self.email, :company_id => self.company_id}
    if db_user = User.find(:first, :select => :id, :conditions => conditions)
      self.errors.add(:email, :user_email_already_taken) if (self.new_record? || self.id != db_user.id)
    end
  end
  
  def update_strings_case
    self.email = self.email.downcase if self.email
    self.firstname = self.firstname.slice(0,1).capitalize + self.firstname.slice(1..-1) if self.firstname
    self.lastname = self.lastname.slice(0,1).capitalize + self.lastname.slice(1..-1) if self.lastname
  end
  
  def validate_old_password
    if self.changing_password && !self.valid_password?(self.current_password)
      errors.add(:current_password, :cant_be_blank.l) if self.current_password.blank?
      errors.add(:current_password, :is_invalid.l)
    end
  end

  def verify_company_user_limit_on_update
    if self.subscription_active_changed? && self.subscription_active
      self.company.create_or_update_user_limit_announcement
    end

    if self.subscription_active_changed? && !self.subscription_active
      self.company.update_or_destroy_user_limit_announcement
    end
  end
  
  def check_errors_on_password
    if self.password.blank?
      errors.instance_values["errors"]["password"].clear if errors.on(:password).present?
      errors.add(:password, :cant_be_blank.l)
    end
    
    if self.password_confirmation.blank?
      errors.instance_values["errors"]["password_confirmation"].clear if errors.on(:password_confirmation).present?
      errors.add(:password_confirmation, :cant_be_blank.l)
    end
  end
  
  protected

  def password_required?
    force_password_validation || crypted_password.blank? || !password.blank?
  end

  def make_activation_code
    self.activation_code = generate_activation_token
  end

  def set_guid
    uuid = SimpleUUID::UUID.new
    self.uuid = uuid.to_guid
  end

end

class String
  def string_titleize
    split(/(\W)/).join
  end
end