module PyCall
  require 'pycall/version'
  require 'pycall/libpython'
  require 'pycall/pyerror'
  require 'pycall/pyobject_wrapper'
  require 'pycall/pytypeobject_wrapper'
  require 'pycall/init'

  module_function

  def builtins
    @builtins ||= wrap_module(LibPython::API.builtins_module_ptr)
  end

  def callable?(obj)
    case obj
    when PyObjectWrapper
      builtins.callable(obj.__pyptr__)
    when PyPtr
      builtins.callable(obj)
    else
      raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
    end
  end

  def dir(obj)
    case obj
    when PyObjectWrapper
      builtins.dir(obj.__pyptr__)
    when PyPtr
      builtins.dir(obj)
    else
      raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
    end
  end

  def eval(expr, globals: nil, locals: nil)
    globals ||= import_module(:__main__).__dict__
    builtins.eval(expr, globals, locals)
  end

  def exec(code, globals: nil, locals: nil)
    globals ||= import_module(:__main__).__dict__
    if PYTHON_VERSION >= '3'
      builtins.exec(code, globals, locals)
    else
      import_module('PyCall.six').exec_(code, globals, locals)
    end
  end

  def import_module(name)
    LibPython::Helpers.import_module(name)
  end

  def len(obj)
    case obj
    when PyObjectWrapper
      builtins.len(obj.__pyptr__)
    when PyPtr
      builtins.len(obj)
    else
      raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
    end
  end

  def sys
    @sys ||= import_module('sys')
  end

  def tuple(iterable=nil)
    pyptr = if iterable
              builtins.tuple.(iterable)
            else
              builtins.tuple.()
            end
    Tuple.wrap_pyptr(pyptr)
  end

  def with(ctx)
    begin
      yield ctx.__enter__
    rescue PyError => err
      raise err unless ctx.__exit__(err.type, err.value, err.traceback)
    rescue Exception => err
      # TODO: support telling what exception has been catched
      raise err unless ctx.__exit__(err.class, err, err.backtrace_locations)
    else
      ctx.__exit__(nil, nil, nil)
    end
  end
end
