# frozen_string_literal: true

require_relative '../../utils/runtime_name_ht'
require_relative '../../utils/command_type'
require_relative '../../utils/command'
require_relative '../../utils/types_handler'

require_relative '../../utils/exceptions/exception_serializer'

require_relative '../../core/reference_cache/references_cache'

require_relative '../../core/handler/handler_dictionary'
require_relative '../../core/handler/abstract_handler'
require_relative '../../core/handler/value_handler'
require_relative '../../core/handler/load_library_handler'
require_relative '../../core/handler/invoke_static_method_handler'
require_relative '../../core/handler/get_static_field_handler'
require_relative '../../core/handler/set_static_field_handler'
require_relative '../../core/handler/create_class_instance_handler'
require_relative '../../core/handler/get_type_handler'
require_relative '../../core/handler/resolve_instance_handler'
require_relative '../../core/handler/get_module_handler'
require_relative '../../core/handler/invoke_instance_method_handler'
require_relative '../../core/handler/exception_handler'
require_relative '../../core/handler/heartbeat_handler'
require_relative '../../core/handler/casting_handler'
require_relative '../../core/handler/get_instance_field_handler'
require_relative '../../core/handler/optimize_handler'
require_relative '../../core/handler/generate_lib_handler'
require_relative '../../core/handler/invoke_global_function_handler'
require_relative '../../core/handler/destruct_reference_handler'
require_relative '../../core/handler/array_reference_handler'
require_relative '../../core/handler/array_get_item_handler'
require_relative '../../core/handler/array_get_size_handler'
require_relative '../../core/handler/array_get_rank_handler'
require_relative '../../core/handler/array_set_item_handler'
require_relative '../../core/handler/array_handler'
require_relative '../../core/handler/retrieve_array_handler'
require_relative '../../core/handler/set_instance_field_handler'
require_relative '../../core/handler/invoke_generic_static_method_handler'
require_relative '../../core/handler/invoke_generic_method_handler'
require_relative '../../core/handler/get_enum_item_handler'
require_relative '../../core/handler/get_enum_name_handler'
require_relative '../../core/handler/get_enum_value_handler'
require_relative '../../core/handler/as_ref_handler'
require_relative '../../core/handler/as_out_handler'
require_relative '../../core/handler/get_ref_value_handler'
require_relative '../../core/handler/enable_namespace_handler'
require_relative '../../core/handler/enable_type_handler'
require_relative '../../core/handler/create_null_handler'
require_relative '../../core/handler/get_static_method_as_delegate_handler'
require_relative '../../core/handler/get_instance_method_as_delegate_handler'
require_relative '../../core/handler/pass_delegate_handler'
require_relative '../../core/handler/invoke_delegate_handler'
require_relative '../../core/handler/convert_type_handler'
require_relative '../../core/handler/add_event_listener_handler'
require_relative '../../core/handler/plugin_wrapper_handler'
require_relative '../../core/handler/get_async_operation_result_handler'
require_relative '../../core/handler/as_kwargs_handler'
require_relative '../../core/handler/get_result_type_handler'
require_relative '../../core/handler/get_global_field_handler'
require_relative '../../core/handler/register_for_update_handler'
require_relative '../../core/handler/value_for_update_handler'
require_relative '../../core/handler/as_dto_handler'
require_relative '../../core/handler/dto_property_handler'
require_relative '../../core/handler/project_result_as_dto_handler'
require_relative '../../core/handler/pass_by_value_handler'
require_relative '../../core/handler/context_metadata_handler'

module Hypertube
  module Core
    module Handler
      class Handler
        def self.initialize_handler_dict
          return unless $handler_dict.empty?

          value_handler = Hypertube::Core::Handler::ValueHandler.new
          load_library_handler = Hypertube::Core::Handler::LoadLibraryHandler.new
          invoke_static_method_handler = Hypertube::Core::Handler::InvokeStaticMethodHandler.new
          get_static_field_handler = Hypertube::Core::Handler::GetStaticFieldHandler.new
          set_static_field_handler = Hypertube::Core::Handler::SetStaticFieldHandler.new
          create_class_instance_handler = Hypertube::Core::Handler::CreateClassInstanceHandler.new
          get_type_handler = Hypertube::Core::Handler::GetTypeHandler.new
          resolve_instance_handler = Hypertube::Core::Handler::ResolveInstanceHandler.new
          get_module_handler = Hypertube::Core::Handler::GetModuleHandler.new
          invoke_instance_method_handler = Hypertube::Core::Handler::InvokeInstanceMethodHandler.new
          exception_handler = Hypertube::Core::Handler::ExceptionHandler.new
          heartbeat_handler = Hypertube::Core::Handler::HeartbeatHandler.new
          casting_handler = Hypertube::Core::Handler::CastingHandler.new
          get_instance_field_handler = Hypertube::Core::Handler::GetInstanceFieldHandler.new
          optimize_handler = Hypertube::Core::Handler::OptimizeHandler.new
          generate_lib_handler = Hypertube::Core::Handler::GenerateLibHandler.new
          invoke_global_function_handler = Hypertube::Core::Handler::InvokeGlobalFunctionHandler.new
          destruct_reference_handler = Hypertube::Core::Handler::DestructReferenceHandler.new
          array_reference_handler = Hypertube::Core::Handler::ArrayReferenceHandler.new
          array_get_item_handler = Hypertube::Core::Handler::ArrayGetItemHandler.new
          array_get_size_handler = Hypertube::Core::Handler::ArrayGetSizeHandler.new
          array_get_rank_handler = Hypertube::Core::Handler::ArrayGetRankHandler.new
          array_set_item_handler = Hypertube::Core::Handler::ArraySetItemHandler.new
          array_handler = Hypertube::Core::Handler::ArrayHandler.new
          retrieve_array_handler = Hypertube::Core::Handler::RetrieveArrayHandler.new
          set_instance_field_handler = Hypertube::Core::Handler::SetInstanceFieldHandler.new
          invoke_generic_static_method_handler = Hypertube::Core::Handler::InvokeGenericStaticMethodHandler.new
          invoke_generic_method_handler = Hypertube::Core::Handler::InvokeGenericMethodHandler.new
          get_enum_item_handler = Hypertube::Core::Handler::GetEnumItemHandler.new
          get_enum_name_handler = Hypertube::Core::Handler::GetEnumNameHandler.new
          get_enum_value_handler = Hypertube::Core::Handler::GetEnumValueHandler.new
          as_ref_handler = Hypertube::Core::Handler::AsRefHandler.new
          as_out_handler = Hypertube::Core::Handler::AsOutHandler.new
          get_ref_value_handler = Hypertube::Core::Handler::GetRefValueHandler.new
          enable_namespace_handler = Hypertube::Core::Handler::EnableNamespaceHandler.new
          enable_type_handler = Hypertube::Core::Handler::EnableTypeHandler.new
          create_null_handler = Hypertube::Core::Handler::CreateNullHandler.new
          get_static_method_as_delegate_handler = Hypertube::Core::Handler::GetStaticMethodAsDelegateHandler.new
          get_instance_method_as_delegate_handler = Hypertube::Core::Handler::GetInstanceMethodAsDelegateHandler.new
          pass_delegate_handler = Hypertube::Core::Handler::PassDelegateHandler.new
          invoke_delegate_handler = Hypertube::Core::Handler::InvokeDelegateHandler.new
          convert_type_handler = Hypertube::Core::Handler::ConvertTypeHandler.new
          add_event_listener_handler = Hypertube::Core::Handler::AddEventListenerHandler.new
          plugin_wrapper_handler = Hypertube::Core::Handler::PluginWrapperHandler.new
          get_async_operation_result_handler = Hypertube::Core::Handler::GetAsyncOperationResultHandler.new
          as_kwargs_handler = Hypertube::Core::Handler::AsKwargsHandler.new
          get_result_type_handler = Hypertube::Core::Handler::GetResultTypeHandler.new
          get_global_field_handler = Hypertube::Core::Handler::GetGlobalFieldHandler.new
          register_for_update_handler = Hypertube::Core::Handler::RegisterForUpdateHandler.new
          value_for_update_handler = Hypertube::Core::Handler::ValueForUpdateHandler.new
          as_dto_handler = Hypertube::Core::Handler::AsDtoHandler.new
          dto_property_handler = Hypertube::Core::Handler::DtoPropertyHandler.new
          project_result_as_dto_handler = Hypertube::Core::Handler::ProjectResultAsDtoHandler.new
          pass_by_value_handler = Hypertube::Core::Handler::PassByValueHandler.new
          context_metadata_handler = Hypertube::Core::Handler::ContextMetadataHandler.new

          $handler_dict[Hypertube::Utils::CommandType::VALUE] = value_handler
          $handler_dict[Hypertube::Utils::CommandType::LOAD_LIBRARY] = load_library_handler
          $handler_dict[Hypertube::Utils::CommandType::INVOKE_STATIC_METHOD] = invoke_static_method_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_STATIC_FIELD] = get_static_field_handler
          $handler_dict[Hypertube::Utils::CommandType::SET_STATIC_FIELD] = set_static_field_handler
          $handler_dict[Hypertube::Utils::CommandType::CREATE_CLASS_INSTANCE] = create_class_instance_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_TYPE] = get_type_handler
          $handler_dict[Hypertube::Utils::CommandType::REFERENCE] = resolve_instance_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_MODULE] = get_module_handler
          $handler_dict[Hypertube::Utils::CommandType::INVOKE_INSTANCE_METHOD] = invoke_instance_method_handler
          $handler_dict[Hypertube::Utils::CommandType::EXCEPTION] = exception_handler
          $handler_dict[Hypertube::Utils::CommandType::HEARTBEAT] = heartbeat_handler
          $handler_dict[Hypertube::Utils::CommandType::CAST] = casting_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_INSTANCE_FIELD] = get_instance_field_handler
          $handler_dict[Hypertube::Utils::CommandType::OPTIMIZE] = optimize_handler
          $handler_dict[Hypertube::Utils::CommandType::GENERATE_LIB] = generate_lib_handler
          $handler_dict[Hypertube::Utils::CommandType::INVOKE_GLOBAL_FUNCTION] = invoke_global_function_handler
          $handler_dict[Hypertube::Utils::CommandType::DESTRUCT_REFERENCE] = destruct_reference_handler
          $handler_dict[Hypertube::Utils::CommandType::ARRAY_REFERENCE] = array_reference_handler
          $handler_dict[Hypertube::Utils::CommandType::ARRAY_GET_ITEM] = array_get_item_handler
          $handler_dict[Hypertube::Utils::CommandType::ARRAY_GET_SIZE] = array_get_size_handler
          $handler_dict[Hypertube::Utils::CommandType::ARRAY_GET_RANK] = array_get_rank_handler
          $handler_dict[Hypertube::Utils::CommandType::ARRAY_SET_ITEM] = array_set_item_handler
          $handler_dict[Hypertube::Utils::CommandType::ARRAY] = array_handler
          $handler_dict[Hypertube::Utils::CommandType::RETRIEVE_ARRAY] = retrieve_array_handler
          $handler_dict[Hypertube::Utils::CommandType::SET_INSTANCE_FIELD] = set_instance_field_handler
          $handler_dict[Hypertube::Utils::CommandType::INVOKE_GENERIC_STATIC_METHOD] = invoke_generic_static_method_handler
          $handler_dict[Hypertube::Utils::CommandType::INVOKE_GENERIC_METHOD] = invoke_generic_method_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_ENUM_ITEM] = get_enum_item_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_ENUM_NAME] = get_enum_name_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_ENUM_VALUE] = get_enum_value_handler
          $handler_dict[Hypertube::Utils::CommandType::AS_REF] = as_ref_handler
          $handler_dict[Hypertube::Utils::CommandType::AS_OUT] = as_out_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_REF_VALUE] = get_ref_value_handler
          $handler_dict[Hypertube::Utils::CommandType::ENABLE_NAMESPACE] = enable_namespace_handler
          $handler_dict[Hypertube::Utils::CommandType::ENABLE_TYPE] = enable_type_handler
          $handler_dict[Hypertube::Utils::CommandType::CREATE_NULL] = create_null_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_STATIC_METHOD_AS_DELEGATE] = get_static_method_as_delegate_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_INSTANCE_METHOD_AS_DELEGATE] = get_instance_method_as_delegate_handler
          $handler_dict[Hypertube::Utils::CommandType::PASS_DELEGATE] = pass_delegate_handler
          $handler_dict[Hypertube::Utils::CommandType::INVOKE_DELEGATE] = invoke_delegate_handler
          $handler_dict[Hypertube::Utils::CommandType::CONVERT_TYPE] = convert_type_handler
          $handler_dict[Hypertube::Utils::CommandType::ADD_EVENT_LISTENER] = add_event_listener_handler
          $handler_dict[Hypertube::Utils::CommandType::PLUGIN_WRAPPER] = plugin_wrapper_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_ASYNC_OPERATION_RESULT] = get_async_operation_result_handler
          $handler_dict[Hypertube::Utils::CommandType::AS_KWARGS] = as_kwargs_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_RESULT_TYPE] = get_result_type_handler
          $handler_dict[Hypertube::Utils::CommandType::GET_GLOBAL_FIELD] = get_global_field_handler
          $handler_dict[Hypertube::Utils::CommandType::REGISTER_FOR_UPDATE] = register_for_update_handler
          $handler_dict[Hypertube::Utils::CommandType::VALUE_FOR_UPDATE] = value_for_update_handler
          $handler_dict[Hypertube::Utils::CommandType::AS_DTO] = as_dto_handler
          $handler_dict[Hypertube::Utils::CommandType::DTO_PROPERTY] = dto_property_handler
          $handler_dict[Hypertube::Utils::CommandType::PROJECT_RESULT_AS_DTO] = project_result_as_dto_handler
          $handler_dict[Hypertube::Utils::CommandType::PASS_BY_VALUE] = pass_by_value_handler
          $handler_dict[Hypertube::Utils::CommandType::CONTEXT_METADATA] = context_metadata_handler
        end

        def self.handle_command(command)
          initialize_handler_dict
          begin
            if command.command_type == Hypertube::Utils::CommandType::PLUGIN_WRAPPER
              raise 'Received a PluginWrapper command, but no receiving plugin is registered.'
            end

            if command.command_type == Hypertube::Utils::CommandType::CONTEXT_METADATA
              $handler_dict[Hypertube::Utils::CommandType::CONTEXT_METADATA].process(command)
              command = command.payload[1]
            end

            response = $handler_dict[command.command_type].handle_command(command)

            parse_response(response, command.runtime_name)
          rescue Exception => e
            Hypertube::Utils::Exceptions::ExceptionSerializer.serialize_exception(e, command)
          end
        end

        private

        def self.parse_response(response, runtime_name)
          # Build base response (primitive vs reference)
          response_command =
            if Hypertube::Utils::TypesHandler.primitive_or_none?(response)
              Hypertube::Utils::Command.create_response(response, runtime_name)
            elsif response.is_a?(Hypertube::Utils::Command)
              response
            else
              reference_cache = Hypertube::Core::ReferenceCache::ReferencesCache.instance
              guid = reference_cache.cache_reference(response)
              Hypertube::Utils::Command.create_reference(guid, runtime_name)
            end

          # Append ValueForUpdate commands from invocation contexts if present
          invocation_contexts = Hypertube::Core::Handler::RegisterForUpdateHandler.get_or_create_context_dict
          if invocation_contexts && !invocation_contexts.empty?
            ref_cache = Hypertube::Core::ReferenceCache::ReferencesCache.instance
            invocation_contexts.each do |key, value|
              instance_guid = ref_cache.cache_reference(value)
              update_context_command = Hypertube::Utils::Command.new(
                runtime_name,
                Hypertube::Utils::CommandType::VALUE_FOR_UPDATE,
                [key.to_s,
                 instance_guid]
              )
              response_command = response_command.add_arg_to_payload(update_context_command)
            end

            invocation_contexts.clear
          end

          response_command
        end

        def self.is_response_array(response)
          response.is_a? Array
        end
      end
    end
  end
end
