# -*- coding: binary -*-

module Rex
  module Java
    module Serialization
      module Model
        # This class provides a newClassDesc representation
        class NewClassDesc < Element

          include Rex::Java::Serialization

          # @!attribute class_name
          #   @return [Rex::Java::Serialization::Model::Utf] The name of the class
          attr_accessor :class_name
          # @!attribute name
          #   @return [Integer] The java class serial version
          attr_accessor :serial_version
          # @!attribute flags
          #   @return [Integer] The java class flags
          attr_accessor :flags
          # @!attribute fields
          #   @return [Array] The java class fields
          attr_accessor :fields
          # @!attribute fields
          #   @return [Rex::Java::Serialization::Model::Annotation] The java class annotations
          attr_accessor :class_annotation
          # @!attribute super_class
          #   @return [Rex::Java::Serialization::Model::ClassDesc] The java class superclass description
          attr_accessor :super_class

          # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to
          def initialize(stream = nil)
            super(stream)
            self.class_name = nil
            self.serial_version = 0
            self.flags = 0
            self.fields = []
            self.class_annotation = nil
            self.super_class = nil
          end

          # Deserializes a Rex::Java::Serialization::Model::ClassDescription
          #
          # @param io [IO] the io to read from
          # @return [self] if deserialization succeeds
          # @raise [RuntimeError] if deserialization doesn't succeed
          def decode(io)
            self.class_name = Utf.decode(io, stream)
            self.serial_version = decode_serial_version(io)
            stream.add_reference(self) unless stream.nil?
            self.flags = decode_flags(io)
            fields_length = decode_fields_length(io)
            fields_length.times do
              field = Field.decode(io, stream)
              self.fields << field
            end

            self.class_annotation = Annotation.decode(io, stream)
            self.super_class = ClassDesc.decode(io, stream)

            self
          end

          # Serializes the Rex::Java::Serialization::Model::ClassDescription
          #
          # @return [String] if serialization succeeds
          # @raise [RuntimeError] if serialization doesn't succeed
          def encode
            unless class_name.class == Rex::Java::Serialization::Model::Utf &&
                    class_annotation.class == Rex::Java::Serialization::Model::Annotation &&
                    super_class.class == Rex::Java::Serialization::Model::ClassDesc
              raise ::RuntimeError, 'Filed to serialize NewClassDesc'
            end
            encoded = ''
            encoded << class_name.encode
            encoded << [serial_version].pack('Q>')
            stream.add_reference(self) unless stream.nil?
            encoded << [flags].pack('C')
            encoded << [fields.length].pack('n')
            fields.each do |field|
              encoded << field.encode
            end
            encoded << class_annotation.encode
            encoded << super_class.encode

            encoded
          end

          # Creates a print-friendly string representation
          #
          # @return [String]
          def to_s
            str = "#{class_name}, [ "
            fields_str = []
            fields.each do |field|
              fields_str << field.to_s
            end
            str << "#{fields_str.join(', ')} ]"

            case super_class.description
            when NewClassDesc
              str << ", @super_class: #{super_class.description.class_name.to_s}"
            when Reference
              str << ", @super_class: #{super_class.description.to_s}"
            end

            str
          end

          private

          # Deserializes a class serial version
          #
          # @param io [IO] the io to read from
          # @return [Integer] if deserialization succeeds
          # @raise [RuntimeError] if deserialization doesn't succeed
          def decode_serial_version(io)
            raw_serial = io.read(8)
            if raw_serial.nil? || raw_serial.length != 8
              raise ::RuntimeError, 'Failed to unserialize ClassDescription'
            end

            raw_serial.unpack('Q>')[0]
          end

          # Deserializes a class flags
          #
          # @param io [IO] the io to read from
          # @return [Integer] if deserialization is possible
          # @raise [RuntimeError] if deserialization doesn't succeed
          def decode_flags(io)
            raw_flags = io.read(1)
            raise ::RuntimeError, 'Failed to unserialize ClassDescription' if raw_flags.nil?

            raw_flags.unpack('C')[0]
          end

          # Deserializes a class fields length
          #
          # @param io [IO] the io to read from
          # @return [Integer] if deserialization is possible
          # @raise [RuntimeError] if deserialization doesn't succeed
          def decode_fields_length(io)
            fields_length = io.read(2)
            if fields_length.nil? || fields_length.length != 2
              raise ::RuntimeError, 'Failed to unserialize ClassDescription'
            end

            fields_length.unpack('n')[0]
          end
        end
      end
    end
  end
end