module VkontakteApi
  class Resolver
    PREDICATE_NAMES = /^(is.*)\?$/
    
    attr_reader :namespace
    
    def initialize(options = {})
      @namespace    = options.delete(:namespace)
      @access_token = options.delete(:access_token)
    end
    
    def method_missing(method_name, *args, &block)
      method_name = method_name.to_s
      
      if Resolver.namespaces.include?(method_name)
        # method with a two-level name called
        Resolver.new(:namespace => method_name, :access_token => @access_token)
      else
        # method with a one-level name called
        name, type = Resolver.vk_method_name(method_name, @namespace)
        
        # adding access_token to the args hash
        args = args.first || {}
        args.update(:access_token => @access_token)
        
        result = API.call(name, args, &block)
        
        if result.respond_to?(:each)
          # enumerable result receives :map with a block when called with a block
          # or is returned untouched otherwise
          block_given? ? result.map(&block) : result
        else
          # non-enumerable result is typecasted
          # (and yielded if block_given?)
          result = typecast(result, type)
          block_given? ? yield(result) : result
        end
      end
    end
  private
    def typecast(parameter, type)
      case type
      when :boolean
        # '1' becomes true, '0' becomes false
        !parameter.to_i.zero?
      else
        parameter
      end
    end
    
    class << self
      attr_reader :namespaces
      
      # load namespaces array from namespaces.yml
      def load_namespaces
        filename    = File.expand_path('../namespaces.yml', __FILE__)
        file        = File.read(filename)
        @namespaces = YAML.load(file)
      end
      
      # vk_method_name('get_country_by_id', 'places')
      # => 'places.getCountryById'
      def vk_method_name(method_name, namespace = nil)
        method_name = method_name.to_s
        
        if method_name =~ PREDICATE_NAMES
          # predicate methods should return true or false
          method_name.sub!(PREDICATE_NAMES, '\1')
          type = :boolean
        else
          # other methods can return anything they want
          type = :anything
        end
        
        full_name = ''
        full_name << convert(namespace) + '.' unless namespace.nil?
        full_name << convert(method_name)
        
        [full_name, type]
      end
    private
      # convert('get_profiles')
      # => 'getProfiles'
      def convert(name)
        name.camelize(:lower)
      end
    end
  end
end

VkontakteApi::Resolver.load_namespaces