class AllSeeingEye::Model
  attr_accessor :id, :score
  
  def self.model_name
    self.to_s.split('::').last.downcase
  end
  
  def self.next_id
    AllSeeingEye.redis.incr("allseeingeye:#{self.model_name}:id")
  end
  
  def self.fields
    AllSeeingEye.configuration[:all_seeing_eye][self.model_name]
  end
  
  def self.field_keys
    self.fields.keys + ['time_spent']
  end
  
  def self.field_names
    (self.field_keys + ['created_at']).flatten
  end
  
  def self.new(fields = {})
    object = super()
    fields.each {|key, value| object.send("#{key}=".to_sym, value)}
    object
  end
  
  def self.create(fields)
    self.new(fields).save
  end
  
  def self.load(object)
    object = Marshal.load(object)
    object.send(:initialize)
  end
  
  def self.find(id)
    self.load(AllSeeingEye.redis["allseeingeye:#{self.model_name}:#{id}"])
  end
  
  def self.all(options = {})
    all = AllSeeingEye.redis.sort("allseeingeye:#{self.model_name}:all", :by => 'nosort', :get => "allseeingeye:#{self.model_name}:*").collect{|o| self.load(o)}
    all = all.find_all{|o| o.created_at > options[:start]} if options[:start]
    all = all.find_all{|o| o.created_at < options[:stop]} if options[:stop]
    all
  end
  
  def self.count(options = {})
    options[:start] = options[:start].to_i if options[:start] && (options[:start].is_a?(Time) || options[:start].is_a?(DateTime) || options[:start].is_a?(Date))
    options[:stop] = options[:stop].to_i if options[:stop] && (options[:stop].is_a?(Time) || options[:stop].is_a?(DateTime) || options[:stop].is_a?(Date))
    options = {:start => '-inf', :stop => '+inf'}.merge(options)

    AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:created_at", options[:start], options[:stop], :with_scores => false).collect{|id| self.find(id)}.size
  end
  
  def self.find_by_field(field, options = {})
    if options[:value]
      all = AllSeeingEye.redis.sort("allseeingeye:#{self.model_name}:fields:#{field}:#{options[:value]}", :by => 'nosort', :get => "allseeingeye:#{self.model_name}:*").collect{|o| self.load(o)}
      all = all.find_all{|o| o.created_at > options[:start]} if options[:start]
      all = all.find_all{|o| o.created_at < options[:stop]} if options[:stop]
      all
    else
      options[:start] = options[:start].to_i
      options[:stop] = options[:stop].to_i
      options = {:start => '-inf', :stop => '+inf'}.merge(options)

      AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:#{field}", options[:start], options[:stop], :with_scores => false).collect{|id| self.find(id)}
    end
  end
  
  def self.count_by_field(field, options = {})
    options = {:start => '-inf', :stop => '+inf'}.merge(options)

    raw_list = AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:#{field}", options[:start], options[:stop], :with_scores => true)
    list = []
    raw_list.each_with_index do |value, index|
      if index % 2 == 0
        list << [value]
      else
        list.last[1] = value.to_i
      end
    end
    list.reverse
  end
  
  def self.first
    self.find(AllSeeingEye.redis.zrange("allseeingeye:#{self.model_name}:fields:created_at", 0, 0))
  end
  
  def self.last
    self.find(AllSeeingEye.redis.zrange("allseeingeye:#{self.model_name}:fields:created_at", -1, -1))
  end
  
  def self.search(query)
    if query =~ /^[\sa-zA-Z0-9,-]*? to [\sa-zA-Z0-9,-]*?$/
      start = Chronic.parse(query.split(' to ').first, :context => :past)
      stop = Chronic.parse(query.split(' to ').last, :context => :past)
      AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:created_at", start.to_i, stop.to_i, :with_scores => false).collect{|id| self.find(id)}
    elsif query =~ /^(#{self.field_names.join('|')}):(.*)$/
      self.find_by_field($1, :value => $2)
    else
      results = []
      self.field_names.each {|f| results << self.find_by_field(f, :value => query)}
      results.flatten.uniq
    end
  end
  
  def self.conglomerate(array, options = {})
    return [] if array.empty?
    
    options = {:granularity => 500}.merge(options)
    if !array.first.score.nil?
      array = array.sort{|a, b| a.score <=> b.score} 
    else
      array = array.sort{|a, b| a.created_at <=> b.created_at}
    end
    start = array.first.created_at.utc.to_i
    stop = array.last.created_at.utc.to_i
    interval = (stop - start) / options[:granularity]
    interval = 1 if interval == 0
    
    tree = RBTree.new
    options[:granularity].times {|n| tree[start + (interval * n)] = 0}
    
    array.each do |o|
      val = o.created_at.utc.to_i
      bound = tree.lower_bound(val)
      bound = tree.upper_bound(val) if bound.nil?
      tree[bound.first] += 1
    end
    tree.to_a
  end
  
  def initialize
    self.class.field_names.each do |key|
      self.class.send(:attr_accessor, key.to_sym)
    end
    self
  end
  
  def id
    @id ||= self.class.next_id
  end
  
  def save
    AllSeeingEye.redis["allseeingeye:#{self.class.model_name}:#{id}"] = Marshal.dump(self)
    AllSeeingEye.redis.sadd("allseeingeye:#{self.class.model_name}:all", id)
    self.class.field_names.each do |field|
      value = self.send(field.to_sym)
      next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
      if value.is_a?(Time) || value.is_a?(DateTime) || value.is_a?(Date)
        AllSeeingEye.redis.zadd("allseeingeye:#{self.class.model_name}:fields:#{field}", value.to_i, id)
      else
        AllSeeingEye.redis.lpush("allseeingeye:#{self.class.model_name}:fields:#{field}:#{value}", id)
        AllSeeingEye.redis.zincrby("allseeingeye:#{self.class.model_name}:fields:#{field}", 1, value)
      end
    end
    self
  end
  
end