module Irc

  require 'socket'

  # wrapped TCPSocket for communication with the server.
  # emulates a subset of TCPSocket functionality
  class IrcSocket
    # server:: server to connect to
    # port::   IRCd port
    # host::   optional local host to bind to (ruby 1.7+ required)
    # create a new IrcSocket
    def initialize(server, port, host, sendfreq=2, maxburst=4)
      @server = server.dup
      @port = port.to_i
      @host = host
      if sendfreq
        @sendfreq = sendfreq.to_f
      else
        @sendfreq = 2
      end
      @last_send = Time.new - @sendfreq
      @burst = 0
      if maxburst
        @maxburst = maxburst.to_i
      else
        @maxburst = 4
      end
    end
    
    # open a TCP connection to the server
    def connect
      if(@host)
        begin
          @sock=TCPSocket.new(@server, @port, @host)
        rescue ArgumentError => e
          $stderr.puts "Your version of ruby does not support binding to a "
          $stderr.puts "specific local address, please upgrade if you wish "
          $stderr.puts "to use HOST = foo"
          $stderr.puts "(this option has been disabled in order to continue)"
          @sock=TCPSocket.new(@server, @port)
        end
      else
        @sock=TCPSocket.new(@server, @port)
      end 
      @qthread = false
      @qmutex = Mutex.new
      @sendq = Array.new
      if (@sendfreq > 0)
        @qthread = Thread.new { spooler }
      end
    end

    def set_sendq(newfreq)
      debug "changing sendq frequency to #{newfreq}"
      @qmutex.synchronize do
        @sendfreq = newfreq
        if newfreq == 0 && @qthread
          clearq
          Thread.kill(@qthread)
          @qthread = false
        elsif(newfreq != 0 && !@qthread)
          @qthread = Thread.new { spooler }
        end
      end
    end

    def set_maxburst(newburst)
      @qmutex.synchronize do
        @maxburst = newburst
      end
    end

    def get_maxburst
      return @maxburst
    end

    def get_sendq
      return @sendfreq
    end
    
    # used to send lines to the remote IRCd
    # message: IRC message to send
    def puts(message)
      @qmutex.synchronize do
        # debug "In puts - got mutex"
        puts_critical(message)
      end
    end

    # get the next line from the server (blocks)
    def gets
      reply = @sock.gets
      if(reply)
        reply.strip!
      end
      debug "RECV: #{reply.inspect}"
      reply
    end

    def queue(msg)
      if @sendfreq > 0
        @qmutex.synchronize do
          # debug "QUEUEING: #{msg}"
          @sendq.push msg
        end
      else
        # just send it if queueing is disabled
        self.puts(msg)
      end
    end

    def spooler
      while true
        spool
        sleep 0.1
      end
    end

    # pop a message off the queue, send it
    def spool
      unless @sendq.empty?
        now = Time.new
        if (now >= (@last_send + @sendfreq))
          # reset burst counter after @sendfreq has passed
          @burst = 0
          debug "in spool, resetting @burst"
        elsif (@burst >= @maxburst)
          # nope. can't send anything
          return
        end
        @qmutex.synchronize do
          debug "(can send #{@maxburst - @burst} lines, there are #{@sendq.length} to send)"
          (@maxburst - @burst).times do
            break if @sendq.empty?
            puts_critical(@sendq.shift)
          end
        end
      end
    end

    def clearq
      unless @sendq.empty?
        @qmutex.synchronize do
          @sendq.clear
        end
      end
    end

    # flush the TCPSocket
    def flush
      @sock.flush
    end

    # Wraps Kernel.select on the socket
    def select(timeout)
      Kernel.select([@sock], nil, nil, timeout)
    end

    # shutdown the connection to the server
    def shutdown(how=2)
      @sock.shutdown(how)
    end

    private
    
    # same as puts, but expects to be called with a mutex held on @qmutex
    def puts_critical(message)
      # debug "in puts_critical"
      debug "SEND: #{message.inspect}"
      @sock.send(message + "\n",0)
      @last_send = Time.new
      @burst += 1
    end

  end

end
