require_relative 'type_deserializer'
require_relative '../../utils/type'
require_relative '../../utils/command'
require_relative '../../utils/string_encoding_mode_ht'
require_relative '../../utils/command_type'
require_relative '../../utils/runtime_name_ht'

module Hypertube
  module Core
    module Protocol
      class CommandDeserializer
        def initialize(buffer)
          @buffer = buffer
          @buffer_len = buffer.length
          @command = Hypertube::Utils::Command.new(buffer[0], buffer[10], [])
          @position = 11
        end

        def is_at_end
          @position == @buffer_len
        end

        def deserialize
          until is_at_end
            @command = @command.add_arg_to_payload(read_object(@buffer[@position]))
          end
          @command
        end

        def read_object(type_num)
          case type_num
          when Hypertube::Utils::Type::COMMAND
            return self.read_command
          when Hypertube::Utils::Type::HT__STRING
            return self.read_string
          when Hypertube::Utils::Type::HT__INTEGER
            return self.read_int
          when Hypertube::Utils::Type::HT__BOOLEAN
            return self.read_bool
          when Hypertube::Utils::Type::HT__FLOAT
            return self.read_float
          when Hypertube::Utils::Type::HT__BYTE
            return self.read_byte
          when Hypertube::Utils::Type::HT__CHAR
            return self.read_char
          when Hypertube::Utils::Type::HT__LONG_LONG
            return self.read_longlong
          when Hypertube::Utils::Type::HT__DOUBLE
            return self.read_double
          when Hypertube::Utils::Type::HT__UNSIGNED_LONG_LONG
            return self.read_ullong
          when Hypertube::Utils::Type::HT__UNSIGNED_INTEGER
            return self.read_uint
          when Hypertube::Utils::Type::HT__NULL
            return self.read_nil
          when Hypertube::Utils::Type::HT__UNDEFINED
            return self.read_undefined
          else
            raise Exception.new("Hypertube::Utils::Type #{type_num} not supported in command deserializer")
          end
        end

        def read_command
          p = @position
          number_of_elements_in_payload = Hypertube::Core::Protocol::TypeDeserializer.deserialize_int(@buffer[p + 1..p + 4])
          runtime = @buffer[p + 5]
          command_type = @buffer[p + 6]
          @position += 7

          payload = []
          i = 0
          while i < number_of_elements_in_payload
            payload << read_object(@buffer[@position])
            i += 1
          end
          Hypertube::Utils::Command.new(runtime, command_type, payload)
        end

        # old implementation
        #  def read_command
        #    p = @position
        #    number_of_elements_in_payload = Hypertube::Core::Protocol::TypeDeserializer.deserialize_int(@buffer[p + 1..p + 4])
        #    runtime = @buffer[p + 5]
        #    command_type = @buffer[p + 6]
        #    @position += 7
        #    return_command = Hypertube::Utils::Command.new(runtime, command_type, [])
        #    read_command_recursively(number_of_elements_in_payload, return_command)
        #  end
        #
        #  def read_command_recursively(number_of_elements_in_payload_left, cmd)
        #    if number_of_elements_in_payload_left == 0
        #      cmd
        #    else
        #      p = @position
        #      cmd = cmd.add_arg_to_payload(self.read_object(@buffer[p]))
        #      read_command_recursively(number_of_elements_in_payload_left - 1, cmd)
        #    end
        #  end

        def read_string
          p = @position
          string_encoding_mode = @buffer[p + 1]
          size = Hypertube::Core::Protocol::TypeDeserializer.deserialize_int(@buffer[p + 2..p + 5])
          @position += 6
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_string(string_encoding_mode, @buffer[p...p + size])
        end

        def read_int
          size = 4
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_int(@buffer[p...p + size])
        end

        def read_bool
          size = 1
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_bool(@buffer[p...p + size])
        end

        def read_float
          size = 4
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_float(@buffer[p...p + size])
        end

        def read_byte
          size = 1
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_byte(@buffer[p...p + size])
        end

        def read_char
          size = 1
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_char(@buffer[p...p + size])
        end

        def read_longlong
          size = 8
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_longlong(@buffer[p...p + size])
        end

        def read_double
          size = 8
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_double(@buffer[p...p + size])
        end

        def read_ullong
          size = 8
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_ullong(@buffer[p...p + size])
        end

        def read_uint
          size = 4
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_uint(@buffer[p...p + size])
        end

        def read_nil
          size = 1
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_nil(@buffer[p...p + size])
        end

        def read_undefined
          size = 1
          @position += 2
          p = @position
          @position += size
          Hypertube::Core::Protocol::TypeDeserializer.deserialize_undefined(@buffer[p...p + size])
        end
      end
    end
  end
end
