require 'thread'

module UV
  class Loop
    module ClassMethods
      # Public: Get default loop
      # 
      # Returns UV::Loop
      def default
        create(UV.default_loop)
      end

      # Public: Create new loop
      # 
      # Returns UV::Loop
      def new
        create(UV.loop_new)
      end

      # Internal: Create custom loop from pointer
      # 
      # Returns UV::Loop
      def create(pointer)
        allocate.tap { |i| i.send(:initialize, FFI::AutoPointer.new(pointer, UV.method(:loop_delete))) }
      end
    end

    extend ClassMethods

    include Resource, Assertions

    # Internal: Initialize a loop using an FFI::Pointer
    # 
    # Returns nothing
    def initialize(pointer) # :notnew:
      @pointer = pointer
      @loop = self
    end

    # Public: Run the actual event loop. This method will block for the duration of event loop
    # 
    # Returns nothing.
    #
    # Raises UV::Error::UNKNOWN
    # Raises UV::Error::EOF
    # Raises UV::Error::EADDRINFO
    # Raises UV::Error::EACCES
    # Raises UV::Error::EAGAIN
    # Raises UV::Error::EADDRINUSE
    # Raises UV::Error::EADDRNOTAVAIL
    # Raises UV::Error::EAFNOSUPPORT
    # Raises UV::Error::EALREADY
    # Raises UV::Error::EBADF
    # Raises UV::Error::EBUSY
    # Raises UV::Error::ECONNABORTED
    # Raises UV::Error::ECONNREFUSED
    # Raises UV::Error::ECONNRESET
    # Raises UV::Error::EDESTADDRREQ
    # Raises UV::Error::EFAULT
    # Raises UV::Error::EHOSTUNREACH
    # Raises UV::Error::EINTR
    # Raises UV::Error::EINVAL
    # Raises UV::Error::EISCONN
    # Raises UV::Error::EMFILE
    # Raises UV::Error::EMSGSIZE
    # Raises UV::Error::ENETDOWN
    # Raises UV::Error::ENETUNREACH
    # Raises UV::Error::ENFILE
    # Raises UV::Error::ENOBUFS
    # Raises UV::Error::ENOMEM
    # Raises UV::Error::ENOTDIR
    # Raises UV::Error::EISDIR
    # Raises UV::Error::ENONET
    # Raises UV::Error::ENOTCONN
    # Raises UV::Error::ENOTSOCK
    # Raises UV::Error::ENOTSUP
    # Raises UV::Error::ENOENT
    # Raises UV::Error::ENOSYS
    # Raises UV::Error::EPIPE
    # Raises UV::Error::EPROTO
    # Raises UV::Error::EPROTONOSUPPORT
    # Raises UV::Error::EPROTOTYPE
    # Raises UV::Error::ETIMEDOUT
    # Raises UV::Error::ECHARSE
    # Raises UV::Error::EAIFAMNOSUPPORT
    # Raises UV::Error::EAISERVICE
    # Raises UV::Error::EAISOCKTYPE
    # Raises UV::Error::ESHUTDOWN
    # Raises UV::Error::EEXIST
    # Raises UV::Error::ESRCH
    # Raises UV::Error::ENAMETOOLONG
    # Raises UV::Error::EPERM
    # Raises UV::Error::ELOOP
    # Raises UV::Error::EXDEV
    # Raises UV::Error::ENOTEMPTY
    # Raises UV::Error::ENOSPC
    def run(run_type = :UV_RUN_DEFAULT)
      check_result! UV.run(@pointer, run_type)
    end

    # Public: (Deprecated - use loop.run with a run_type specified) Runs outstanding events once, yields control back
    # 
    # Returns nothing
    def run_once
      run(:UV_RUN_ONCE)
    end

    # Public: forces loop time update, useful for getting more granular times
    # 
    # Returns nothing
    def update_time
      UV.update_time(@pointer)
    end

    # Public: Get current time in microseconds
    # 
    # Returns timestamp with microseconds as an Integer
    def now
      UV.now(@pointer)
    end

    # Internal: Get last error from the loop
    # 
    # Returns one of UV::Error or nil
    def lookup_error(err)
      name = UV.err_name(err)
      msg  = UV.strerror(err)

      return nil if name == "OK"

      Error.const_get(name.to_sym).new(msg)
    end

    # Public: Get a new timer instance
    # 
    # Returns UV::Timer
    def timer
      timer_ptr = UV.create_handle(:uv_timer)

      check_result! UV.timer_init(@pointer, timer_ptr)
      Timer.new(self, timer_ptr)
    end

    # Public: Get a new TCP instance
    # 
    # Returns UV::TCP instance
    def tcp
      tcp_ptr = UV.create_handle(:uv_tcp)

      check_result! UV.tcp_init(@pointer, tcp_ptr)
      TCP.new(self, tcp_ptr)
    end

    # Public: Get a new UDP instance
    #
    # Returns UV::UDP instance
    def udp
      udp_ptr = UV.create_handle(:uv_udp)

      check_result! UV.udp_init(@pointer, udp_ptr)
      UV::UDP.new(self, udp_ptr)
    end

    # Public: Get a new TTY instance
    # 
    # fileno   - Integer file descriptor of a tty device.
    # readable - Boolean wether TTY is readable or not
    # 
    # Returns UV::TTY
    # 
    # Raises ArgumentError if fileno argument is not an Integer
    # Raises Argument error if readable is not a Boolean
    def tty(fileno, readable = false)
      assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
      assert_boolean(readable)

      tty_ptr = UV.create_handle(:uv_tty)

      check_result! UV.tty_init(@pointer, tty_ptr, fileno, readable ? 1 : 0)
      TTY.new(self, tty_ptr)
    end

    # Public: Get a new Pipe instance
    # 
    # ipc - Boolean wether handle will be used for ipc, useful for sharing tcp socket
    #       between processes
    # 
    # Returns UV::Pipe
    def pipe(ipc = false)
      assert_boolean(ipc)

      pipe_ptr = UV.create_handle(:uv_pipe)

      check_result! UV.pipe_init(@pointer, pipe_ptr, ipc ? 1 : 0)
      Pipe.new(self, pipe_ptr)
    end

    # Public: Get a new Prepare handle
    # 
    # Returns UV::Prepare
    def prepare
      prepare_ptr = UV.create_handle(:uv_prepare)

      check_result! UV.prepare_init(@pointer, prepare_ptr)
      Prepare.new(self, prepare_ptr)
    end

    # Public: Get a new Check handle
    # 
    # Returns UV::Check
    def check
      check_ptr = UV.create_handle(:uv_check)

      check_result! UV.check_init(@pointer, check_ptr)
      Check.new(self, check_ptr)
    end

    # Public: Get a new Idle handle
    # 
    # Returns UV::Handle
    def idle
      idle_ptr = UV.create_handle(:uv_idle)

      check_result! UV.idle_init(@pointer, idle_ptr)
      Idle.new(self, idle_ptr)
    end

    # Public: Get a new Async handle
    # 
    # Returns UV::Async
    # 
    # Raises ArgumentError if block is not given and is not expecting one argument exactly
    def async(&block)
      assert_block(block)
      assert_arity(1, block)

      async_ptr = UV.create_handle(:uv_async)
      async     = Async.new(self, async_ptr, &block)

      check_result! UV.async_init(@pointer, async_ptr, async.callback(:on_async))
      async
    end

    # Public: Do some work in the libuv thread pool
    #
    # Returns UV::Work
    #
    # Raises ArgumentError if block is not given and if the block or optional callback are expecting any arguments
    def work(callback = nil, op = nil, &block)
      block = block || op
      assert_block(block)
      assert_arity(0, block)

      if not callback.nil?
        assert_block(callback)
        assert_arity(0, callback)
      end

      work = Work.new(self, block, callback)
    end

    # Public: Get a new Filesystem instance
    # 
    # Returns UV::Filesystem
    def fs
      Filesystem.new(self)
    end

    # Public: Get a new FSEvent instance
    # 
    # Returns UV::FSEvent
    def fs_event(path, &block)
      assert_block(block)
      assert_arity(3, block)

      fs_event_ptr = UV.create_handle(:uv_fs_event)
      fs_event     = FSEvent.new(self, fs_event_ptr, &block)

      check_result! UV.fs_event_init(@pointer, fs_event_ptr, path, fs_event.callback(:on_fs_event), 0)
      fs_event
    end

    # Internal: Get a hold of internal loop pointer instance
    # 
    # Returns FFI::Pointer
    def to_ptr
      @pointer
    end
  end
end