#--
# Facets System
# Copyright (c) Thomas Sawyer
#
# Ruby License
#
# This module is free software. You may use, modify, and/or redistribute this
# software under the same terms as Ruby.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.
#++

# Author::    Thomas Sawyer
# Copyright:: Copyright (c) 2005 Thomas Sawyer
# License::   Ruby License

# = Facets System
#
# This is a central "meta" controller for the Facets system.
# It can be used to dynmically gleen information about Facets.
# Or used to load a facet give the symbol of the actual method.
#
# == Synopsis
#
#  TODO
#

module Facets

  extend self

  OPERATORS = %w{ +@ -@ + - ** * / % ~ <=> << >> < > === == =~ <= >= | & ^ []= [] }
  OPERATORS_REGEXP = Regexp.new( '(' << OPERATORS.collect{ |k| Regexp.escape(k) }.join('|') << ')' )
  OPERATORS_ESCAPE = {
     "+@"   => "op_plus_self",
     "-@"   => "op_minus_self",
     "+"    => "op_plus",
     "-"    => "op_minus",
     "**"   => "op_pow",
     "*"    => "op_mul",
     "/"    => "op_div",
     "%"    => "op_mod",
     "~"    => "op_tilde",
     "<=>"  => "op_cmp",
     "<<"   => "op_lshift",
     ">>"   => "op_rshift",
     "<"    => "op_lt",
     ">"    => "op_gt",
     "==="  => "op_case_eq",
     "=="   => "op_equal",
     "=~"   => "op_apply",
     "<="   => "op_lt_eq",
     ">="   => "op_gt_eq",
     "|"    => "op_or",
     "&"    => "op_and",
     "^"    => "op_xor",
     "[]="  => "op_store",
     "[]"   => "op_fetch"
  }

  CLASS_ANCESTORS = {
    'array'         => ['enumerable','kernel'],
    'binding'       => ['kernel'],
    'class'         => ['module','kernel'],
    'comparable'    => ['kernel'],
    'continuation'  => ['kernel'],
    'date'          => ['comparable','kernel'],
    'dir'           => ['enumerable','kernel'],
    'enumerable'    => ['kernel'],
    'file'          => ['io','enumerable','kernel'],
    'fileutils'     => ['kernel'],
    'float'         => ['precision','numeric','comparable','kernel'],
    'hash'          => ['enumerable','kernel'],
    'integer'       => ['precision','numeric','comparable','kernel'],
    'kernel'        => ['kernel'],
    'logger'        => ['kernel'],
    'matchdata'     => ['kernel'],
    'module'        => ['kernel'],
    'nilclass'      => ['kernel'],
    'numeric'       => ['comparable','kernel'],
    'ostruct'       => ['kernel'],
    'pathname'      => ['kernel'],
    'proc'          => ['kernel'],
    'range'         => ['enumerable','kernel'],
    'regexp'        => ['kernel'],
    'string'        => ['enumerable','comparable','kernel'],
    'symbol'        => ['kernel'],
    'time'          => ['comparable','kernel'],
    'unboundmethod' => ['kernel'],
  }

  NOAUTO = %w{
    array/op_fetch.rb
    array/op_store.rb
    binding/self/of_caller.rb
    hash/op_fetch.rb
    hash/op_store.rb
    numeric/succ.rb
    numeric/pred.rb
    string/succ.rb
    string/unpack.rb
    module/attr.rb
    hash/each.rb
    module/methods.rb
    object/method.rb
    nilclass/op_fetch.rb
    nilclass/empty.rb
    nilclass/include.rb
    nilclass/size.rb
    nilclass/length.rb
    nilclass/method_missing.rb
    date/*
    fileutils/*
    ostruct/*
    logger/*
    gem/*
  }

  METHDIR = "facet"

  def op_esc( str )
    str = str.to_s.chomp('.rb')
    str = str.strip.gsub(OPERATORS_REGEXP){ OPERATORS_ESCAPE[$1] }
    str.chomp('?').chomp('!').chomp('=')
  end

  # Subs terms for Ruby's operators
  # and removes any trailing ? ! or =.
  def op_esc_path( name )
    ri = name.rindex('/')
    if ri
      dir, file = name[0...ri], name[ri+1..-1]
    else
      dir, file = nil, name
    end
    file = op_esc(file.strip)
    dir ? File.join( dir, file ) : file
  end

  # Returns a file list of all extensions.
  def extensions
    return @all_extensions if @all_extensions
    all = nil
    Dir.chdir( File.join( Config::CONFIG['sitelibdir'], METHDIR ) ) do
      all = Dir.glob( '*/*.rb' ) + Dir.glob( '*/*/*.rb' )
    end
    @extensions = all
  end

  # Returns file list of all extensions minus the ones that should not be autoloaded.
  def extensions_nice
    @safe_extensions ||= nice_only( extensions )
  end

  # Removes the NOAUTO files from the given extensions list.
  def nice_only( exts )
    exts = exts.dup
    NOAUTO.each do |f|
      if f.include?('*')
        re = %r"#{f.gsub('*','.*')}"
        exts.reject!{ |x| x =~ re }
      elsif exts.include?(f)
        exts.delete f
      end
    end
    exts.sort
  end

  #--
  # NOTE Is there any other way to handle subclasses?
  #++

  alias_method :require_facetless, :require

  def require( klass, fname )
    klass = klass.to_s.downcase
    fname = op_esc(fname.to_s)
    if CLASS_ANCESTORS.key?( klass )
      ancestors = [klass, *CLASS_ANCESTORS[klass]]
      begin
        require_facetless( File.join( METHDIR, klass, fname ) )
      rescue LoadError => e
        klass = ancestors.shift
        retry if klass
        raise e
      end
    else
      require_facetless( File.join(METHDIR, klass.name.downcase, fname) )
    end
  end

end


# Override Kernel require method to reroute Facet requires.
#
#module Kernel
#
#  alias_method :require_facetless, :require
#
#  def require( fname )
#    if /^(facet|facets|nano)[\/\\]/ =~ fname
#      Facets.require($')
#    else
#      require_facetless( fname )
#    end
#  end
#
#end


