module RedisAnalytics
  class Visit
    include Metrics

    # This class represents one unique visit
    # User may have never visited the site
    # User may have visited before but his visit is expired
    # Everything counted here is unique for a visit

    # helpers
    def for_each_time_range(t)
      RedisAnalytics.redis_key_timestamps.map{|x, y| t.strftime(x)}.each do |ts|
        yield(ts)
      end
    end

    def first_visit_info
      cookie = @rack_request.cookies[RedisAnalytics.first_visit_cookie_name]
      return cookie ? cookie.split('.') : []
    end

    def current_visit_info
      cookie = @rack_request.cookies[RedisAnalytics.current_visit_cookie_name]
      return cookie ? cookie.split('.') : []
    end

    # method used in analytics.rb to initialize visit
    def initialize(request, response)
      @t = Time.now
      @redis_key_prefix = "#{RedisAnalytics.redis_namespace}:"
      @rack_request = request
      @rack_response = response
      @first_visit_seq = first_visit_info[0] || current_visit_info[0]
      @current_visit_seq = current_visit_info[1]

      @first_visit_time = first_visit_info[1]
      @last_visit_time = first_visit_info[2]

      @page_view_seq_no = current_visit_info[2] || 0
      @last_visit_start_time = current_visit_info[3]
      @last_visit_end_time = current_visit_info[4]
    end

    # called from analytics.rb
    def record
      if @current_visit_seq
        track("visit_time", @t.to_i - @last_visit_end_time.to_i)
      else
        @current_visit_seq ||= counter("visits")
        track("visits", 1) # track core 'visit' metric
        if @first_visit_seq
          track("repeat_visits", 1)
        else
          @first_visit_seq ||= counter("unique_visits")
          track("first_visits", 1)
          track("unique_visits", 1)
        end
        exec_custom_methods('visit') # custom methods that are measured on a per-visit basis
      end
      exec_custom_methods('hit') # custom methods that are measured on a per-page-view (per-hit) basis
      track("page_views", 1) # track core 'page_view' metric
      track("second_page_views", 1) if @page_view_seq_no.to_i == 1 # @last_visit_start_time and (@last_visit_start_time.to_i == @last_visit_end_time.to_i)
      @rack_response
    end

    def exec_custom_methods(type)
      Metrics.public_instance_methods.each do |meth|
        if m = meth.to_s.match(/^([a-z_]*)_(count|ratio)_per_#{type}$/)
          begin
            return_value = self.send(meth)
            track(m.to_a[1], return_value) if return_value
          rescue => e
            warn "#{meth} resulted in an exception #{e}"
          end
        end
      end
    end

    # helpers
    def counter(metric_name)
      n = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}#{metric_name}")
      # to debug, uncomment this line
      # puts "COUNT #{metric_name} -> #{n}"
      return n
    end

    def updated_current_visit_info
      value = [@first_visit_seq.to_i, @current_visit_seq.to_i, @page_view_seq_no.to_i + 1, (@last_visit_start_time || @t).to_i, @t.to_i]
      # to debug, uncomment this line
      # puts "UPDATING VCN COOKIE -> #{value}"
      expires = @t + RedisAnalytics.visit_timeout.to_i.minutes
      {:value => value.join('.'), :expires => expires}
    end

    def updated_first_visit_info
      value = [@first_visit_seq.to_i, (@first_visit_time || @t).to_i, @t.to_i]
      # to debug, uncomment this line
      # puts "UPDATING RUCN COOKIE -> #{value}"
      expires = @t + 1.year
      {:value => value.join('.'), :expires => expires}
    end

    def track(metric_name, metric_value)
      n = 0
      RedisAnalytics.redis_connection.hmset("#{@redis_key_prefix}#METRICS", metric_name, metric_value.class)
      for_each_time_range(@t) do |ts|
        key = "#{@redis_key_prefix}#{metric_name}:#{ts}"
        if metric_value.is_a?(Integer)
          n = RedisAnalytics.redis_connection.incrby(key, metric_value)
        else
          n = RedisAnalytics.redis_connection.zincrby(key, 1, metric_value)
        end
      end
      # to debug, uncomment this line
      # puts "TRACK #{metric_name} -> #{n}" 
      return n
    end

  end
end

