# encoding: utf-8
module Mongoid  #:nodoc:
    module Gator
      class Gatorer

        include Readers
        
        # Initialize object
        def initialize(object, field)
           @object, @for = object, field
           create_accessors()
        end

        private
        
        # Get total for
        def total_for(date,grain,opts={})
         unless date.nil?
           begin
             return  @object.class.where(create_query_hash(date,grain,opts)).sum(@for)
           rescue
             return  @object.where(create_query_hash(date,grain,opts)).sum(@for)
          end
         end
        end
        
        # Get collections for
        def collection_for(date,grain,opts={})
          unless date.nil?
            return  @object.collection.group(:keyf => create_fkey(grain,0), 
                    :reduce => "function(obj,prev){for (var key in obj.#{@for}) {prev.#{@for} += obj.#{@for}}}",
                    :cond=>create_query_hash(date,grain,opts),
                    :initial => {@for => 0})
          end
        end
        
        # Get collections for group
        def collection_for_group(date,grain,off_set,opts={})
          unless date.nil?
            return  @object.collection.group(:key => create_group_key_hash, 
                    :reduce => "function(obj,prev){for (var key in obj.#{@for}) {prev.#{@for} += obj.#{@for}}}",
                    :cond=>create_query_hash(date,grain,opts),
                    :initial => {@for => 0})
          end
        end
        
        # Convert date levels
        def convert_date_by_level(date,level)
          if date.is_a?(Range)
            sdate = date.first
            edate = date.last
          else
            sdate = date
            edate = date
          end
          case level
          when HOUR
            return sdate.change(:sec=>0), edate.change(:sec=>0)
          when DAY
            return sdate.change(:hour=>0).change(:sec=>0), edate.change(:hour=>0).change(:sec=>0) + 1.day
          when MONTH
            return sdate.change(:day=>1).change(:hour=>0).change(:sec=>0), edate.change(:day=>1).change(:hour=>0).change(:sec=>0) + 1.month
          end
        end
        
        # Create fkey
        def create_fkey(grain,off_set)
          case grain
          when HOUR
            fkey = Javascript.aggregate_hour
          when MONTH
            fkey = "function(doc) {
                off_set = #{off_set} * 1000;
                dd = new Date((doc.date + #{off_set})* 1000);
                utc_date = parseInt((Date.parse((dd.getUTCMonth() + 1) + '/' + '01' + '/' + dd.getUTCFullYear()) / 1000)/86400)*86400;
                out = (utc_date - #{off_set});
                return {date:  out.toFixed(0)};
                }"

          else # DEFAULT TO DAY
	          fkey = "function(doc) {
            	  off_set = #{off_set} * 1000;
             	  dd = new Date((doc.date + #{off_set})* 1000);
		            utc_date = parseInt((Date.parse((dd.getUTCMonth() + 1) + '/' + dd.getUTCDate() + '/' + dd.getUTCFullYear()) / 1000)/86400)*86400;
		            out = (utc_date - #{off_set});
            	return {date:  out.toFixed(0)};
     	    	}"
          end
          return fkey
        end
        
        protected   
        def create_accessors
          self.class.class_eval do 
            define_method :inc  do |  *args |       
              keys, date = gen_params(Hash[*args])
              inc_counter(keys,date)
            end
            
            define_method :dec  do | *args |       
                keys, date = gen_params(Hash[*args])
                dec_counter(keys,date)
            end
            
            define_method :add  do | how_many, *args |       
                keys, date = gen_params(Hash[*args])
                add_to_counter(how_many,keys,date)
            end
            
            define_method :reset  do |  *args |       
                keys, date = gen_params(Hash[*args])
                reset_counter(keys,date)
            end
          end
        end
        
        # Add
        def add_to_counter(how_much = 1, keys=[], date = Time.now)
          return if how_much == 0
          # Upsert value
          @object.collection.update(create_key_hash(keys,date.utc),
            {"$inc" => {
              "#{@for}" => how_much,
            }},
              :upsert => true
            )
        end
        
        # Reset Counter
        def reset_counter(keys=[], date = Time.now)
          # Upsert value
          @object.collection.update(create_key_hash(keys,date.utc),
            {"$set" => {
              "#{@for}" => 0,
            }},
              :upsert => true
            )
        end
        
        # Increment Counter
        def inc_counter(keys,date = Time.now)
          add_to_counter(1,keys,date)
        end
        
        # Decrement Counter
        def dec_counter(keys, date = Time.now)
           add_to_counter(-1,keys,date)
        end
        
        # Generate parameters
        def gen_params(params)
          date = Time.now # Set default date to now
          key_hash = Hash.new { |hash, key| hash[key] = [] }
          @object.gator_keys.each do| gk |
            raise Errors::ModelNotSaved, "Missing key value #{gk}" if params[gk].nil?
            key_hash[gk] = params[gk]
          end
          # Set Date 
          if !params[:date].nil?
            date = params[:date]
          end
          return key_hash, date
        end
        
        # Create Hash Key
        def create_key_hash(keys,date = Time.now)
          keys = Hash[keys]
          key_hash = Hash.new { |hash, key| hash[key] = [] }
          @object.gator_keys.each do | gk |
            if  keys[gk].kind_of?(Array)
              keys[gk].each do |k|
                key_hash[gk] = k
              end
            else
              key_hash[gk] = keys[gk]
            end
          end
          key_hash[:date] = normalize_date(date)
          return key_hash
        end
        
        # Create Group Key Hash
        def create_group_key_hash
          keys = []
          @object.gator_keys.each do | gk |
            keys << gk
          end
          return keys
        end
        
        # Create Query Hash
        def create_query_hash(date = Time.now, grain, opts)
          key_hash = Hash.new { |hash, key| hash[key] = [] }
          # Set Keys
          if !opts.empty?
            @object.gator_keys.each do | gk |
              raise Errors::ModelNotSaved, "Missing key value #{gk}" if opts[gk].nil?
              if  opts[gk].kind_of?(Array)
                key_hash[gk] = {"$in" =>  opts[gk]}
              else
                key_hash[gk] = opts[gk]
              end
            end
          end
          
          sdate,edate = convert_date_by_level(date,grain) # Set Dates
          key_hash[:date] = {"$gte" => normalize_date(sdate), "$lt" => normalize_date(edate + 1.day)}
          return key_hash
        end
        
        # Normalize Dates
        def normalize_date(date)
          case date
          when String
            date =  Time.parse(date).change(:sec => 0).change(:min => 0)
          when Date
            date = date.to_time.change(:sec => 0).change(:min => 0)
          when Range
            date = normalize_date(date.change(:sec => 0).change(:min => 0).first)..normalize_date(date.change(:sec => 0).change(:min => 0).last)
          else
            date = date.change(:sec => 0).change(:min => 0)
          end
          return date.to_i
          
        end
          
      end  
    end
end