## Index interface, subclassed by Ferret indexer.

require 'fileutils'

begin
  require 'chronic'
  $have_chronic = true
rescue LoadError => e
  debug "No 'chronic' gem detected. Install it for date/time query restrictions."
  $have_chronic = false
end

module Redwood

class BaseIndex
  include InteractiveLock

  class LockError < StandardError
    def initialize h
      @h = h
    end

    def method_missing m; @h[m.to_s] end
  end

  def is_a_deprecated_ferret_index?; false end

  include Singleton

  def initialize dir=BASE_DIR
    @dir = dir
    @lock = Lockfile.new lockfile, :retries => 0, :max_age => nil
    @sync_worker = nil
    @sync_queue = Queue.new
  end

  def lockfile; File.join @dir, "lock" end

  def lock
    debug "locking #{lockfile}..."
    begin
      @lock.lock
    rescue Lockfile::MaxTriesLockError
      raise LockError, @lock.lockinfo_on_disk
    end
  end

  def start_lock_update_thread
    @lock_update_thread = Redwood::reporting_thread("lock update") do
      while true
        sleep 30
        @lock.touch_yourself
      end
    end
  end

  def stop_lock_update_thread
    @lock_update_thread.kill if @lock_update_thread
    @lock_update_thread = nil
  end

  def unlock
    if @lock && @lock.locked?
      debug "unlocking #{lockfile}..."
      @lock.unlock
    end
  end

  def load
    SourceManager.load_sources
    load_index
  end

  def save
    debug "saving index and sources..."
    FileUtils.mkdir_p @dir unless File.exists? @dir
    SourceManager.save_sources
    save_index
  end

  def load_index
    unimplemented
  end

  def add_message m; unimplemented end
  def update_message m; unimplemented end
  def update_message_state m; unimplemented end

  def save_index fn
    unimplemented
  end

  def contains_id? id
    unimplemented
  end

  def contains? m; contains_id? m.id end

  def size
    unimplemented
  end

  def empty?; size == 0 end

  ## Yields a message-id and message-building lambda for each
  ## message that matches the given query, in descending date order.
  ## You should probably not call this on a block that doesn't break
  ## rather quickly because the results can be very large.
  def each_id_by_date query={}
    unimplemented
  end

  ## Return the number of matches for query in the index
  def num_results_for query={}
    unimplemented
  end

  ## yield all messages in the thread containing 'm' by repeatedly
  ## querying the index. yields pairs of message ids and
  ## message-building lambdas, so that building an unwanted message
  ## can be skipped in the block if desired.
  ##
  ## only two options, :limit and :skip_killed. if :skip_killed is
  ## true, stops loading any thread if a message with a :killed flag
  ## is found.
  def each_message_in_thread_for m, opts={}
    unimplemented
  end

  ## Load message with the given message-id from the index
  def build_message id
    unimplemented
  end

  ## Delete message with the given message-id from the index
  def delete id
    unimplemented
  end

  ## Given an array of email addresses, return an array of Person objects that
  ## have sent mail to or received mail from any of the given addresses.
  def load_contacts email_addresses, h={}
    unimplemented
  end

  ## Yield each message-id matching query
  def each_id query={}
    unimplemented
  end

  ## Yield each message matching query
  def each_message query={}, &b
    each_id query do |id|
      yield build_message(id)
    end
  end

  ## Implementation-specific optimization step
  def optimize
    unimplemented
  end

  ## Return the id source of the source the message with the given message-id
  ## was synced from
  def source_for_id id
    unimplemented
  end

  class ParseError < StandardError; end

  ## parse a query string from the user. returns a query object
  ## that can be passed to any index method with a 'query'
  ## argument.
  ##
  ## raises a ParseError if something went wrong.
  def parse_query s
    unimplemented
  end

  def save_thread t
    t.each_dirty_message do |m|
      if @sync_worker
        @sync_queue << m
      else
        update_message_state m
      end
      m.clear_dirty
    end
  end

  def start_sync_worker
    @sync_worker = Redwood::reporting_thread('index sync') { run_sync_worker }
  end

  def stop_sync_worker
    return unless worker = @sync_worker
    @sync_worker = nil
    @sync_queue << :die
    worker.join
  end

  def run_sync_worker
    while m = @sync_queue.deq
      return if m == :die
      update_message_state m
      # Necessary to keep Xapian calls from lagging the UI too much.
      sleep 0.03
    end
  end
end

## just to make the backtraces even more insane, here we engage in yet more
## method_missing metaprogramming so that Index.init(index_type_name) will
## magically make Index act like the correct Index class.
class Index
  def self.init type=nil
    ## determine the index type from the many possible ways of setting it
    type = (type == "auto" ? nil : type) ||
      ENV['SUP_INDEX'] ||
      $config[:index] ||
      (File.exist?(File.join(BASE_DIR, "xapian")) && "xapian") || ## PRIORITIZE THIS
      (File.exist?(File.join(BASE_DIR, "ferret")) && "ferret") || ## deprioritize this
      DEFAULT_NEW_INDEX_TYPE
    begin
      require "sup/#{type}_index"
      @klass = Redwood.const_get "#{type.capitalize}Index"
      @obj = @klass.init
    rescue LoadError, NameError => e
      raise "unknown index type #{type.inspect}: #{e.message}"
    end
    debug "using #{type} index"
    @obj
  end

  def self.instance; @obj end
  def self.method_missing m, *a, &b; @obj.send(m, *a, &b) end
  def self.const_missing x; @obj.class.const_get(x) end
end

end