process (Irc::Client)

    # File lib/rbot/rfc2812.rb, line 1025
        def process(serverstring)
          data = Hash.new
          data[:serverstring] = serverstring

          unless serverstring.chomp =~ /^(:(\S+)\s)?(\S+)(\s(.*))?$/
            raise "Unparseable Server Message!!!: #{serverstring.inspect}"
          end

          prefix, command, params = $2, $3, $5

          if prefix != nil
            # Most servers will send a full nick!user@host prefix for
            # messages from users. Therefore, when the prefix doesn't match this
            # syntax it's usually the server hostname.
            #
            # This is not always true, though, since some servers do not send a
            # full hostmask for user messages.
            #
            if prefix =~ /^#{Regexp::Irc::BANG_AT}$/
              data[:source] = @server.user(prefix)
            else
              if @server.hostname
                if @server.hostname != prefix
                  # TODO do we want to be able to differentiate messages that are passed on to us from /other/ servers?
                  debug "Origin #{prefix} for message\n\t#{serverstring.inspect}\nis neither a user hostmask nor the server hostname\nI'll pretend that it's from the server anyway"
                  data[:source] = @server
                else
                  data[:source] = @server
                end
              else
                @server.instance_variable_set(:@hostname, prefix)
                data[:source] = @server
              end
            end
          end

          # split parameters in an array
          argv = []
          params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params

          if command =~ /^(\d+)$/ # Numeric replies
            data[:target] = argv[0]
            # A numeric reply /should/ be directed at the client, except when we're connecting with a used nick, in which case
            # it's directed at '*'
            not_us = !([@user.nick, '*'].include?(data[:target]))
            if not_us
              warning "Server reply #{serverstring.inspect} directed at #{data[:target]} instead of client (#{@user.nick})"
            end

            num=command.to_i
            case num
            when RPL_WELCOME
              data[:message] = argv[1]
              # "Welcome to the Internet Relay Network
              # <nick>!<user>@<host>"
              if not_us
                warning "Server thinks client (#{@user.inspect}) has a different nick"
                @user.nick = data[:target]
              end
              if data[:message] =~ /([^@!\s]+)(?:!([^@!\s]+?))?@(\S+)/
                nick = $1
                user = $2
                host = $3
                warning "Welcome message nick mismatch (#{nick} vs #{data[:target]})" if nick != data[:target]
                @user.user = user if user
                @user.host = host if host
              end
              handle(:welcome, data)
            when RPL_YOURHOST
              # "Your host is <servername>, running version <ver>"
              data[:message] = argv[1]
              handle(:yourhost, data)
            when RPL_CREATED
              # "This server was created <date>"
              data[:message] = argv[1]
              handle(:created, data)
            when RPL_MYINFO
              # "<servername> <version> <available user modes>
              # <available channel modes>"
              @server.parse_my_info(params.split(' ', 2).last)
              data[:servername] = @server.hostname
              data[:version] = @server.version
              data[:usermodes] = @server.usermodes
              data[:chanmodes] = @server.chanmodes
              handle(:myinfo, data)
            when RPL_ISUPPORT
              # "PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
              # "MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63
              # TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=#
              # PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer :are available
              # on this server"
              #
              @server.parse_isupport(argv[1..-2].join(' '))
              handle(:isupport, data)
            when ERR_NICKNAMEINUSE
              # "* <nick> :Nickname is already in use"
              data[:nick] = argv[1]
              data[:message] = argv[2]
              handle(:nicktaken, data)
            when ERR_ERRONEUSNICKNAME
              # "* <nick> :Erroneous nickname"
              data[:nick] = argv[1]
              data[:message] = argv[2]
              handle(:badnick, data)
            when RPL_TOPIC
              data[:channel] = @server.channel(argv[1])
              data[:topic] = argv[2]
              data[:channel].topic.text = data[:topic]

              handle(:topic, data)
            when RPL_TOPIC_INFO
              data[:nick] = @server.user(argv[0])
              data[:channel] = @server.channel(argv[1])

              # This must not be an IRC::User because it might not be an actual User,
              # and we risk overwriting valid User data
              data[:source] = argv[2].to_irc_netmask(:server => @server)

              data[:time] = Time.at(argv[3].to_i)

              data[:channel].topic.set_by = data[:source]
              data[:channel].topic.set_on = data[:time]

              handle(:topicinfo, data)
            when RPL_NAMREPLY
              # "( "=" / "*" / "@" ) <channel>
              # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
              # - "@" is used for secret channels, "*" for private
              # channels, and "=" for others (public channels).
              data[:channeltype] = argv[1]
              data[:channel] = chan = @server.channel(argv[2])

              users = []
              argv[3].scan(/\S+/).each { |u|
                # FIXME beware of servers that allow multiple prefixes
                if(u =~ /^([#{@server.supports[:prefix][:prefixes].join}])?(.*)$/)
                  umode = $1
                  user = $2
                  users << [user, umode]
                end
              }

              users.each { |ar|
                u = @server.user(ar[0])
                chan.add_user(u, :silent => true)
                debug "Adding user #{u}"
                if ar[1]
                  ms = @server.mode_for_prefix(ar[1].to_sym)
                  debug "\twith mode #{ar[1]} (#{ms})"
                  chan.mode[ms].set(u)
                end
              }
              @tmpusers += users
            when RPL_ENDOFNAMES
              data[:channel] = @server.channel(argv[1])
              data[:users] = @tmpusers
              handle(:names, data)
              @tmpusers = Array.new
            when RPL_LUSERCLIENT
              # ":There are <integer> users and <integer>
              # services on <integer> servers"
              data[:message] = argv[1]
              handle(:luserclient, data)
            when RPL_LUSEROP
              # "<integer> :operator(s) online"
              data[:ops] = argv[1].to_i
              handle(:luserop, data)
            when RPL_LUSERUNKNOWN
              # "<integer> :unknown connection(s)"
              data[:unknown] = argv[1].to_i
              handle(:luserunknown, data)
            when RPL_LUSERCHANNELS
              # "<integer> :channels formed"
              data[:channels] = argv[1].to_i
              handle(:luserchannels, data)
            when RPL_LUSERME
              # ":I have <integer> clients and <integer> servers"
              data[:message] = argv[1]
              handle(:luserme, data)
            when ERR_NOMOTD
              # ":MOTD File is missing"
              data[:message] = argv[1]
              handle(:motd_missing, data)
            when RPL_LOCALUSERS
              # ":Current local  users: 3  Max: 4"
              data[:message] = argv[1]
              handle(:localusers, data)
            when RPL_GLOBALUSERS
              # ":Current global users: 3  Max: 4"
              data[:message] = argv[1]
              handle(:globalusers, data)
            when RPL_STATSCONN
              # ":Highest connection count: 4 (4 clients) (251 since server was
              # (re)started)"
              data[:message] = argv[1]
              handle(:statsconn, data)
            when RPL_MOTDSTART
              # "<nick> :- <server> Message of the Day -"
              if argv[1] =~ /^-\s+(\S+)\s/
                server = $1
              else
                warning "Server doesn't have an RFC compliant MOTD start."
              end
              @motd = ""
            when RPL_MOTD
              if(argv[1] =~ /^-\s+(.*)$/)
                @motd << $1
                @motd << "\n"
              end
            when RPL_ENDOFMOTD
              data[:motd] = @motd
              handle(:motd, data)
            when RPL_DATASTR
              data[:text] = argv[1]
              handle(:datastr, data)
            when RPL_AWAY
              data[:nick] = user = @server.user(argv[1])
              data[:message] = argv[-1]
              user.away = data[:message]
              handle(:away, data)
            when RPL_WHOREPLY
              data[:channel] = channel = @server.channel(argv[1])
              data[:user] = argv[2]
              data[:host] = argv[3]
              data[:userserver] = argv[4]
              data[:nick] = user = @server.user(argv[5])
              if argv[6] =~ /^(H|G)(\*)?(.*)?$/
                data[:away] = ($1 == 'G')
                data[:ircop] = $2
                data[:modes] = $3.scan(/./).map { |mode|
                  m = @server.supports[:prefix][:prefixes].index(mode.to_sym)
                  @server.supports[:prefix][:modes][m]
                } rescue []
              else
                warning "Strange WHO reply: #{serverstring.inspect}"
              end
              data[:hopcount], data[:real_name] = argv[7].split(" ", 2)

              user.user = data[:user]
              user.host = data[:host]
              user.away = data[:away] # FIXME doesn't provide the actual message
              # TODO ircop status
              # TODO userserver
              # TODO hopcount
              user.real_name = data[:real_name]

              channel.add_user(user, :silent=>true)
              data[:modes].map { |mode|
                channel.mode[mode].set(user)
              }

              handle(:who, data)
            when RPL_ENDOFWHO
              handle(:eowho, data)
            when RPL_WHOISUSER
              @whois ||= Hash.new
              @whois[:nick] = argv[1]
              @whois[:user] = argv[2]
              @whois[:host] = argv[3]
              @whois[:real_name] = argv[-1]

              user = @server.user(@whois[:nick])
              user.user = @whois[:user]
              user.host = @whois[:host]
              user.real_name = @whois[:real_name]
            when RPL_WHOISSERVER
              @whois ||= Hash.new
              @whois[:nick] = argv[1]
              @whois[:server] = argv[2]
              @whois[:server_info] = argv[-1]
              # TODO update user info
            when RPL_WHOISOPERATOR
              @whois ||= Hash.new
              @whois[:nick] = argv[1]
              @whois[:operator] = argv[-1]
              # TODO update user info
            when RPL_WHOISIDLE
              @whois ||= Hash.new
              @whois[:nick] = argv[1]
              user = @server.user(@whois[:nick])
              @whois[:idle] = argv[2].to_i
              user.idle_since = Time.now - @whois[:idle]
              if argv[-1] == 'seconds idle, signon time'
                @whois[:signon] = Time.at(argv[3].to_i)
                user.signon = @whois[:signon]
              end
            when RPL_ENDOFWHOIS
              @whois ||= Hash.new
              @whois[:nick] = argv[1]
              data[:whois] = @whois.dup
              @whois.clear
              handle(:whois, data)
            when RPL_WHOISCHANNELS
              @whois ||= Hash.new
              @whois[:nick] = argv[1]
              @whois[:channels] = []
              user = @server.user(@whois[:nick])
              argv[-1].split.each do |prechan|
                pfx = prechan.scan(/[#{@server.supports[:prefix][:prefixes].join}]/)
                modes = pfx.map { |p| @server.mode_for_prefix p }
                chan = prechan[pfx.length..prechan.length]

                channel = @server.channel(chan)
                channel.add_user(user, :silent => true)
                modes.map { |mode| channel.mode[mode].set(user) }

                @whois[:channels] << [chan, modes]
              end
            when RPL_CHANNELMODEIS
              parse_mode(serverstring, argv[1..-1], data)
              handle(:mode, data)
            when RPL_CREATIONTIME
              data[:channel] = @server.channel(argv[1])
              data[:time] = Time.at(argv[2].to_i)
              data[:channel].creation_time=data[:time]
              handle(:creationtime, data)
            when RPL_CHANNEL_URL
              data[:channel] = @server.channel(argv[1])
              data[:url] = argv[2]
              data[:channel].url=data[:url].dup
              handle(:channel_url, data)
            when ERR_NOSUCHNICK
              data[:nick] = argv[1]
              if user = @server.get_user(data[:nick])
                @server.delete_user(user)
              end
              handle(:nosuchnick, data)
            when ERR_NOSUCHCHANNEL
              data[:channel] = argv[1]
              if channel = @server.get_channel(data[:channel])
                @server.delete_channel(channel)
              end
              handle(:nosuchchannel, data)
            else
              warning "Unknown message #{serverstring.inspect}"
              handle(:unknown, data)
            end
            return # We've processed the numeric reply
          end

          # Otherwise, the command should be a single word
          case command.to_sym
          when :PING
            data[:pingid] = argv[0]
            handle(:ping, data)
          when :PONG
            data[:pingid] = argv[0]
            handle(:pong, data)
          when :PRIVMSG
            # you can either bind to 'PRIVMSG', to get every one and
            # parse it yourself, or you can bind to 'MSG', 'PUBLIC',
            # etc and get it all nicely split up for you.

            begin
              data[:target] = @server.user_or_channel(argv[0])
            rescue
              # The previous may fail e.g. when the target is a server or something
              # like that (e.g. $<mask>). In any of these cases, we just use the
              # String as a target
              # FIXME we probably want to explicitly check for the #<mask> $<mask>
              data[:target] = argv[0]
            end
            data[:message] = argv[1]
            handle(:privmsg, data)

            # Now we split it
            if data[:target].kind_of?(Channel)
              handle(:public, data)
            else
              handle(:msg, data)
            end
          when :NOTICE
            begin
              data[:target] = @server.user_or_channel(argv[0])
            rescue
              # The previous may fail e.g. when the target is a server or something
              # like that (e.g. $<mask>). In any of these cases, we just use the
              # String as a target
              # FIXME we probably want to explicitly check for the #<mask> $<mask>
              data[:target] = argv[0]
            end
            data[:message] = argv[1]
            case data[:source]
            when User
              handle(:notice, data)
            else
              # "server notice" (not from user, noone to reply to)
              handle(:snotice, data)
            end
          when :KICK
            data[:channel] = @server.channel(argv[0])
            data[:target] = @server.user(argv[1])
            data[:message] = argv[2]

            @server.delete_user_from_channel(data[:target], data[:channel])
            if data[:target] == @user
              @server.delete_channel(data[:channel])
            end

            handle(:kick, data)
          when :PART
            data[:channel] = @server.channel(argv[0])
            data[:message] = argv[1]

            @server.delete_user_from_channel(data[:source], data[:channel])
            if data[:source] == @user
              @server.delete_channel(data[:channel])
            end

            handle(:part, data)
          when :QUIT
            data[:message] = argv[0]
            data[:was_on] = @server.channels.inject(ChannelList.new) { |list, ch|
              list << ch if ch.has_user?(data[:source])
              list
            }

            @server.delete_user(data[:source])

            handle(:quit, data)
          when :JOIN
            data[:channel] = @server.channel(argv[0])
            data[:channel].add_user(data[:source])

            handle(:join, data)
          when :TOPIC
            data[:channel] = @server.channel(argv[0])
            data[:topic] = Channel::Topic.new(argv[1], data[:source], Time.new)
            data[:channel].topic.replace(data[:topic])

            handle(:changetopic, data)
          when :INVITE
            data[:target] = @server.user(argv[0])
            data[:channel] = @server.channel(argv[1])

            handle(:invite, data)
          when :NICK
            data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
              list << ch if ch.has_user?(data[:source])
              list
            }

            data[:newnick] = argv[0]
            data[:oldnick] = data[:source].nick.dup
            data[:source].nick = data[:newnick]

            debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"

            handle(:nick, data)
          when :MODE
            parse_mode(serverstring, argv, data)
            handle(:mode, data)
          else
            warning "Unknown message #{serverstring.inspect}"
            handle(:unknown, data)
          end
        end