require_relative './exception_type'

module Hypertube
  module Utils
    module Exceptions
      class ExceptionSerializer
        def self.serialize_exception(exception, command)
          exception_command = Hypertube::Utils::Command.new(command.runtime_name, Hypertube::Utils::CommandType::EXCEPTION, [])
          begin
            stack_trace = exception.backtrace
            exception_message = exception.message
            exception_name = exception.class.to_s

            stack_classes = ''
            stack_methods = ''
            stack_lines = ''
            stack_files = ''
            if !stack_trace.nil? && !stack_trace.empty?
              stack_trace.each_with_index do |value, index|
                stack_file, stack_class, stack_line, stack_method = parse_stack_frame(value)
                next if stack_file.include?('hypertube-ruby-sdk') or stack_file.include?('Binaries/Ruby')

                append_to_string(stack_classes, stack_class)
                append_to_string(stack_methods, stack_method)
                append_to_string(stack_lines, stack_line)
                append_to_string(stack_files, stack_file)
                next unless index != stack_trace.length - 1

                append_to_string(stack_classes, '|')
                append_to_string(stack_methods, '|')
                append_to_string(stack_lines, '|')
                append_to_string(stack_files, '|')
              end
            end

            exception_command = exception_command.add_arg_to_payload(get_exception_code(exception_name.to_s))
            exception_command = exception_command.add_arg_to_payload(command.to_s)
            exception_command = exception_command.add_arg_to_payload(exception_name.to_s)
            exception_command = exception_command.add_arg_to_payload(exception_message)
            exception_command = exception_command.add_arg_to_payload(stack_classes)
            exception_command = exception_command.add_arg_to_payload(stack_methods)
            exception_command = exception_command.add_arg_to_payload(stack_lines)
            exception_command = exception_command.add_arg_to_payload(stack_files)
          rescue Exception => e
            exception_command = Hypertube::Utils::Command.new(Hypertube::Utils::RuntimeNameHt::RUBY, Hypertube::Utils::CommandType::EXCEPTION, [])
            exception_command = exception_command.add_arg_to_payload(Hypertube::Utils::Exceptions::ExceptionType::EXCEPTION)
            exception_command = if command.nil?
                                  exception_command.add_arg_to_payload('Hypertube::Utils::Command is nil')
                                else
                                  exception_command.add_arg_to_payload(command.to_s)
                                end
            exception_command = exception_command.add_arg_to_payload('Ruby Exception Serialization Error')
            exception_command = exception_command.add_arg_to_payload(e.message)
            exception_command = exception_command.add_arg_to_payload('Hypertube::Utils::Exceptions::ExceptionSerializer')
            exception_command = exception_command.add_arg_to_payload('serialize_exception')
            exception_command = exception_command.add_arg_to_payload('undefined')
            exception_command = exception_command.add_arg_to_payload(__FILE__)
          end
          exception_command
        end

        def self.append_to_string(string, value)
          string << if value.nil?
                      'undefined'
                    else
                      value
                    end
        end

        def self.parse_stack_frame(stack_frame)
          match = /^(.+):(\d+):in [`'](.+)[`']$/.match(stack_frame)
          return %w[undefined undefined undefined undefined] if match.nil?

          stack_file, stack_line, method_info = match.captures

          case method_info
          when /^(.+)#(.+)$/
            stack_class = ::Regexp.last_match(1).split('::').last || 'undefined'
            stack_method = ::Regexp.last_match(2)
          when /^(.+)\.(.+)$/
            # handle Class.method
            stack_class = ::Regexp.last_match(1).split('::').last || 'undefined'
            stack_method = ::Regexp.last_match(2)
          when /^<(.+)>$/
            stack_class = 'undefined'
            stack_method = "<#{::Regexp.last_match(1)}>"
          else
            # no class information — e.g. top-level method or block
            stack_class = 'undefined'
            stack_method = method_info
          end

          [stack_file, stack_class, stack_line, stack_method]
        end

        def self.get_exception_code(exception_name)
          case exception_name
          when 'Exception'
            Hypertube::Utils::Exceptions::ExceptionType::EXCEPTION
          when 'IOError'
            Hypertube::Utils::Exceptions::ExceptionType::IO_EXCEPTION
          when 'Errno::ENOENT'
            Hypertube::Utils::Exceptions::ExceptionType::FILE_NOT_FOUND_EXCEPTION
          when 'RuntimeError'
            Hypertube::Utils::Exceptions::ExceptionType::RUNTIME_EXCEPTION
          when 'ZeroDivisionError'
            Hypertube::Utils::Exceptions::ExceptionType::ARITHMETIC_EXCEPTION
          when 'ArgumentError'
            Hypertube::Utils::Exceptions::ExceptionType::ILLEGAL_ARGUMENT_EXCEPTION
          when 'IndexError'
            Hypertube::Utils::Exceptions::ExceptionType::INDEX_OUT_OF_BOUNDS_EXCEPTION
          when 'TypeError'
            Hypertube::Utils::Exceptions::ExceptionType::NULL_POINTER_EXCEPTION
          else
            Hypertube::Utils::Exceptions::ExceptionType::EXCEPTION
          end
        end
      end
    end
  end
end
