text: '''
  Copyright (c) 2011-2017, Agora Games, LLC All rights reserved.
  
  https://github.com/agoragames/haigha/blob/master/LICENSE.txt
  '''
  
  from collections import deque
  
  from haigha2.writer import Writer
  from haigha2.reader import Reader
  from haigha2.frames.frame import Frame
  
  
  class HeaderFrame(Frame):
  
      '''
      Header frame for content.
      '''
      PROPERTIES = [
          ('content_type', 'shortstr', Reader.read_shortstr,
           Writer.write_shortstr, 1 << 15),
          ('content_encoding', 'shortstr', Reader.read_shortstr,
           Writer.write_shortstr, 1 << 14),
          ('application_headers', 'table',
              Reader.read_table, Writer.write_table, 1 << 13),
          ('delivery_mode', 'octet', Reader.read_octet,
              Writer.write_octet, 1 << 12),
          ('priority', 'octet', Reader.read_octet, Writer.write_octet, 1 << 11),
          ('correlation_id', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 10),
          ('reply_to', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 9),
          ('expiration', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 8),
          ('message_id', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 7),
          ('timestamp', 'timestamp', Reader.read_timestamp,
              Writer.write_timestamp, 1 << 6),
          ('type', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 5),
          ('user_id', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 4),
          ('app_id', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 3),
          ('cluster_id', 'shortstr', Reader.read_shortstr,
              Writer.write_shortstr, 1 << 2)
      ]
      DEFAULT_PROPERTIES = True
  
      @classmethod
      def type(cls):
          return 2
  
      @property
      def class_id(self):
          return self._class_id
  
      @property
      def weight(self):
          return self._weight
  
      @property
      def size(self):
          return self._size
  
      @property
      def properties(self):
          return self._properties
  
      @classmethod
      def parse(self, channel_id, payload):
          '''
          Parse a header frame for a channel given a Reader payload.
          '''
          class_id = payload.read_short()
          weight = payload.read_short()
          size = payload.read_longlong()
          properties = {}
  
          # The AMQP spec is overly-complex when it comes to handling header
          # frames. The spec says that in addition to the first 16bit field,
          # additional ones can follow which /may/ then be in the property list
          # (because bit flags aren't in the list). Properly implementing custom
          # values requires the ability change the properties and their types,
          # which someone is welcome to do, but seriously, what's the point?
          # Because the complexity of parsing and writing this frame directly
          # impacts the speed at which messages can be processed, there are two
          # branches for both a fast parse which assumes no changes to the
          # properties and a slow parse. For now it's up to someone using custom
          # headers to flip the flag.
          if self.DEFAULT_PROPERTIES:
              flag_bits = payload.read_short()
              for key, proptype, rfunc, wfunc, mask in self.PROPERTIES:
                  if flag_bits & mask:
                      properties[key] = rfunc(payload)
          else:
              flags = []
              while True:
                  flag_bits = payload.read_short()
                  flags.append(flag_bits)
                  if flag_bits & 1 == 0:
                      break
  
              shift = 0
              for key, proptype, rfunc, wfunc, mask in self.PROPERTIES:
                  if shift == 0:
                      if not flags:
                          break
                      flag_bits, flags = flags[0], flags[1:]
                      shift = 15
                  if flag_bits & (1 << shift):
                      properties[key] = rfunc(payload)
                  shift -= 1
  
          return HeaderFrame(channel_id, class_id, weight, size, properties)
  
      def __init__(self, channel_id, class_id, weight, size, properties={}):
          Frame.__init__(self, channel_id)
          self._class_id = class_id
          self._weight = weight
          self._size = size
          self._properties = properties
  
      def __str__(self):
          return "%s[channel: %d, class_id: %d, weight: %d, size: %d, properties: %s]" % (
              self.__class__.__name__, self.channel_id, self._class_id,
              self._weight, self._size, self._properties)
  
      def write_frame(self, buf):
          '''
          Write the frame into an existing buffer.
          '''
          writer = Writer(buf)
          writer.write_octet(self.type())
          writer.write_short(self.channel_id)
  
          # Track the position where we're going to write the total length
          # of the frame arguments.
          stream_args_len_pos = len(buf)
          writer.write_long(0)
  
          stream_method_pos = len(buf)
  
          writer.write_short(self._class_id)
          writer.write_short(self._weight)
          writer.write_longlong(self._size)
  
          # Like frame parsing, branch to faster code for default properties
          if self.DEFAULT_PROPERTIES:
              # Track the position where we're going to write the flags.
              flags_pos = len(buf)
              writer.write_short(0)
              flag_bits = 0
              for key, proptype, rfunc, wfunc, mask in self.PROPERTIES:
                  val = self._properties.get(key, None)
                  if val is not None:
                      flag_bits |= mask
                      wfunc(writer, val)
              writer.write_short_at(flag_bits, flags_pos)
          else:
              shift = 15
              flag_bits = 0
              flags = []
              stack = deque()
              for key, proptype, rfunc, wfunc, mask in self.PROPERTIES:
                  val = self._properties.get(key, None)
                  if val is not None:
                      if shift == 0:
                          flags.append(flag_bits)
                          flag_bits = 0
                          shift = 15
  
                      flag_bits |= (1 << shift)
                      stack.append((wfunc, val))
  
                  shift -= 1
  
              flags.append(flag_bits)
              for flag_bits in flags:
                  writer.write_short(flag_bits)
              for method, val in stack:
                  method(writer, val)
  
          # Write the total length back at the beginning of the frame
          stream_len = len(buf) - stream_method_pos
          writer.write_long_at(stream_len, stream_args_len_pos)
  
          writer.write_octet(0xce)
  
  HeaderFrame.register()
  