module Potato
  module DAmn
    # Parser for dAmn packets.
    class Packet
      # Matches packets that should contain a body.
      BODIED = [
        /^recv p?chat:(.*?)\n\nmsg main/,
        /^recv p?chat:(.*?)\n\naction main/,
        /^property p?chat:(.*?)\np=(topic|title|privclasses)/,
        /^recv p?chat:(.*?)\n\nkicked/,
        /^recv p?chat:(.*?)\n\nadmin show/,
        /^recv p?chat:(.*?)\n\nadmin privclass/,
        /^kicked/
      ]
      # All existing tablumps.
      TABLUMPS = (%w[&b &/b &i &/i &u &/u &s &/s &sup
        &/sup &sub &/sub &code &/code &br
        &ul &/ul &ol &/ol &li &/li &bcode
        &/bcode &/a &/acro &/abbr &p &/p].map{|lump| lump + "\t" } +
        [/&emote\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t/,
        /&a\t([^\t]+)\t([^\t]*)\t/,
        /&link\t([^\t]+)\t&\t/,
        /&link\t([^\t]+)\t([^\t]+)\t&\t/,
        /&dev\t[^\t]\t([^\t]+)\t/,
        /&avatar\t([^\t]+)\t[0-9]+\t/,
        /&thumb\t([0-9]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t/,
        /&img\t([^\t]+)\t([^\t]*)\t([^\t]*)\t/,
        /&iframe\t([^\t]+)\t([0-9%]*)\t([0-9%]*)\t&\/iframe\t/,
        /&acro\t([^\t]+)\t/,
        /&abbr\t([^\t]+)\t/,
        /^.+?<abbr title="(.+?)"><\/abbr>:/]).
        zip(["\x02", "\x0F", "\x16", "\x0F", "\x1F", "\x0F", "<s>", "</s>",
             "<sup>", "</sup>", "<sub>", "</sub>", "<code>", "</code>", "<br>",
             "<ul>", "</ul>", "<ol>", "</ol>", "<li>", "</li>", "", "", "</a>",
             "</acronym>", "", "<p>", "</p>", '\1', '<a href="\1" title="\2">',
             '\1', '\1 (\2)', ':dev\1:', ':icon\1:', ':thumb\1:',
             '<img src="\1" alt="\2" title="\3" />', '<iframe src="\1" width="\2" height="\3" />',
             '<acronym title="\1">', '', '\1:', ''])

      # @param [String] pkt the string received from the server to parse
      # @raise [PacketNilException] if the string is nil, meaning that the server connection has been closed
      def initialize pkt
        TABLUMPS.each{|find, replace| pkt.gsub!(find, replace)}
        @response = {:cmd => nil, :args => {}, :param => nil, :subpkts => [], :raw => pkt, :body => ""}
        chunks = pkt.split(/\n\n/)
        details = chunks.shift.split(/\n/)
        @response[:cmd], @response[:param] = *details.shift.split(" ")
        details.each{|dt|
          key, value = *dt.split(/\W/, 2)
          next if key.empty?
          @response[:args][key.to_sym] = CGI.unescapeHTML(value)
        }
        body = BODIED.map{|x|!!(pkt =~ x)}.include?(true)
        range = if body then chunks[0...-1] else chunks end
        range.each{|c|
          @response[:subpkts] << self.class.new(c)
        }
        if body && chunks[-1]
          if pkt =~ /\n\n$/
            @response[:subpkts] << self.class.new(chunks[-1])
          else
            @response[:body] = CGI.unescapeHTML(chunks[-1].chomp("\0").decode_entities)
          end
        end
      end

      # @return [String] the unformatted chat name
      def room
        if @response[:param] =~ /p?chat:/ then @response[:param].sub(/p?chat:/, "") else nil end
      end

      # @return [Boolean] whether the current packet has an error or not
      def ok?
        @response[:args][:e].nil? || @response[:args][:e] == "ok"
      end

      # @return [String] the current packet error
      def error
        @response[:args][:e]
      end

      # @yield [argument] iterates through each argument
      def each &blk
        @response[:args].each(&blk)
      end

      # @param [String] key the key to retrieve from the arguments
      # @return [String] the packet argument
      def [](key) @response[:args][key.to_sym] end

      # @return [Packet] the first subpacket
      def subpkt
        @response[:subpkts][0]
      end

      # Lookup @response entries, struct-style.
      def method_missing(m, *args, &blk) @response[m] end
      
      # Generates a human-readable representation of this packet
      # @return [String]
      def inspect
        str = "#<Potato::DAmn::Packet '#{@response[:cmd]} #{@response[:param]}'"
        str << ", args={#{@response[:args].map{|k,v|
          "#{k}: #{v}"
        }.join(", ")}}" if @response[:args].size > 0
        str << ", body='#{@response[:body].rstrip}'" if @response[:body]
        str << ", subpackets=#{@response[:subpkts]}" if @response[:subpkts].size > 0
        str << ">"
        str
      end
    end
  end
end