require 'rbbt/resource/util'
require 'yaml'

module Path
  attr_accessor :resource, :pkgdir, :search_paths

  def self.setup(string, pkgdir = nil, resource = nil, search_paths = nil)
    string.extend Path
    string.pkgdir = pkgdir || 'rbbt'
    string.resource = resource
    string.search_paths = search_paths
    string
  end

  def sub(*args)
    self.annotate super(*args)
  end

  def annotate(name)
    Path.setup name.to_s, @pkgdir, @resource, @search_paths
  end

  def join(name)
    if self.empty?
      self.annotate name.to_s.dup
    else
      self.annotate File.join(self, name.to_s)
    end
  end

  def dirname
    Path.setup File.dirname(self), @pkgdir, @resource
  end

  def directory?
    return nil unless self.exists?
    File.directory? self.find 
  end

  def glob(pattern = '*')
    return [] unless self.exists?
    exp = File.join(self.find, pattern)
    Dir.glob(exp).collect{|f| Path.setup(f, self.resource, self.pkgdir)}
  end

  def [](name, orig = false)
    return super(name) if orig
    join name
  end

  def byte(pos)
    send(:[], pos, true)
  end

  def method_missing(name, prev = nil, *args, &block)
    if block_given?
      super name, prev, *args, &block
    else
      # Fix problem with ruby 1.9 calling methods by its own initiative. ARG
      super(name, prev, *args) if name.to_s =~ /^to_/
      if prev.nil?
        join name
      else
        join(prev).join(name)
      end
    end
  end

  SEARCH_PATHS = {
    :current => File.join("{PWD}", "{TOPLEVEL}", "{SUBPATH}"),
    :user    => File.join(ENV['HOME'], ".{PKGDIR}", "{TOPLEVEL}", "{SUBPATH}"),
    :global  => File.join('/', "{TOPLEVEL}", "{PKGDIR}", "{SUBPATH}"),
    :local   => File.join('/usr/local', "{TOPLEVEL}", "{PKGDIR}", "{SUBPATH}"),
    :lib     => File.join('{LIBDIR}', "{TOPLEVEL}", "{SUBPATH}"),
    :default => :user
  }

  search_path_file = File.join(ENV['HOME'], '.rbbt/etc/search_paths')
  if File.exists?(search_path_file)
    YAML.load(File.open(search_path_file)).each do |where, location|
      SEARCH_PATHS[where.to_sym] = location
    end
  end

  def find(where = nil, caller_lib = nil, paths = nil)
    paths = (self.search_paths || SEARCH_PATHS).merge(paths || {})
    where = paths[:default] if where == :default
    return self if located?
    if self.match(/(.*?)\/(.*)/)
      toplevel, subpath = self.match(/(.*?)\/(.*)/).values_at 1, 2
    else
      toplevel, subpath = self, ""
    end

    path = nil
    if where.nil?
      %w(current user local global lib).each do |w| 
        w = w.to_sym
        next unless paths.include? w
        path = find(w, caller_lib, paths)
        return path if File.exists? path
      end
      if paths.include? :default
        find((paths[:default] || :user), caller_lib, paths)
      else
        raise "Path '#{ path }' not found, and no default specified in search paths: #{paths.inspect}"
      end
    else
      where = where.to_sym
      raise "Did not recognize the 'where' tag: #{where}. Options: #{paths.keys}" unless paths.include? where

      if where == :lib
        libdir = Path.caller_lib_dir(caller_lib) || "NOLIBDIR"
      else
        libdir = "NOLIBDIR"
      end
      pwd = FileUtils.pwd
      path = paths[where].
        sub('{PKGDIR}', pkgdir).
        sub('{PWD}', pwd).
        sub('{TOPLEVEL}', toplevel).
        sub('{SUBPATH}', subpath).
        sub('{PATH}', self).
        sub('{LIBDIR}', libdir) #, @pkgdir, @resource, @search_paths
      self.annotate path
    end
  end

  def find_all(caller_lib = nil, search_paths = nil)
    search_paths ||= self.search_paths || SEARCH_PATHS
    search_paths = search_paths.dup

    search_paths.keys.
      collect{|where| find(where, Path.caller_lib_dir, search_paths) }.
      compact.select{|file| file.exists? }.uniq
  end

  #{{{ Methods

  def in_dir?(dir)
    ! ! File.expand_path(self).match(/^#{Regexp.quote dir}/)
  end

  def to_s
    self.find
  end

  def filename
    self.find
  end

  def exists?
    begin
      self.produce
      File.exists? self.find
    rescue
      false
    end
  end

  def produce(force = false)
    path = self.find

    return self if Open.exists?(path.to_s) and not force

    raise "No resource defined to produce file: #{ self }" if resource.nil?

    resource.produce self, force

    self
  end

  def read(&block)
    Open.read(self.produce.find, &block)
  end

  def write(*args, &block)
    Open.write(self.produce.find, *args, &block)
  end


  def open(options = {}, &block)
    Open.open(self.produce.find, options, &block)
  end

  def to_s
    "" + self
  end

  def basename
    Path.setup(File.basename(self), self.resource, self.pkgdir)
  end

  def tsv(*args)
    TSV.open(self.produce, *args)
  end

  def list
    Open.read(self.produce.find).split "\n"
  end

  def keys(field = 0, sep = "\t")
    Open.read(self.produce.find).split("\n").collect{|l| next if l =~ /^#/; l.split(sep, -1)[field]}.compact
  end

  def yaml
    self.open do |f|
      YAML.load f
    end
  end

  def pipe_to(cmd, options = {})
    CMD.cmd(cmd, {:in => self.open, :pipe => true}.merge(options))
  end

  def index(options = {})
    TSV.index(self.produce, options)
  end

  def range_index(start, eend, options = {})
    TSV.range_index(self.produce, start, eend, options)
  end

  def pos_index(pos, options = {})
    TSV.pos_index(self.produce, pos, options)
  end

  def to_yaml(*args)
    self.to_s.to_yaml(*args)
  end

  def fields
    TSV.parse_header(self.open).fields
  end

  def all_fields
    self.open do |stream|
      TSV.parse_header(stream).all_fields
    end
  end

  def identifier_file_path
    if self.dirname.identifiers.exists?
      self.dirname.identifiers
    else
      nil
    end
  end

  def identifier_files
    if identifier_file_path.nil?
      []
    else
      [identifier_file_path]
    end
  end

  def set_extension(new_extension = nil)
    new_path = self.sub(/\.[^\.\/]+$/, "." << new_extension.to_s)
    Path.setup new_path, @pkgdir, @resource
  end

  def doc_file(relative_to = 'lib')
    if located?
      lib_dir = Path.caller_lib_dir(self, relative_to)
      relative_file = File.join( 'doc', self.sub(lib_dir,''))
      Path.setup File.join(lib_dir, relative_file) , @pkgdir, @resource
    else
      Path.setup File.join('doc', self) , @pkgdir, @resource
    end
  end

  def source_for_doc_file(relative_to = 'lib')
    if located?
      lib_dir = Path.caller_lib_dir(Path.caller_lib_dir(self, 'doc'), relative_to)
      relative_file = self.sub(/(.*\/)doc\//, '\1').sub(lib_dir + "/",'')
      file = File.join(lib_dir, relative_file)

      if not File.exists?(file)
        file= Dir.glob(file.sub(/\.[^\.\/]+$/, '.*')).first
      end

      Path.setup file, @pkgdir, @resource
    else
      relative_file = self.sub(/^doc\//, '\1')

      if not File.exists?(relative_file)
        relative_file = Dir.glob(relative_file.sub(/\.[^\.\/]+$/, '.*')).first
      end

      Path.setup relative_file , @pkgdir, @resource
    end
  end
end