<commit_before>module Odk
  # Takes a response and odk data. Parses odk data into answer tree for response.
  class ResponseParser
    attr_accessor :response, :raw_odk_xml

    #initialize in a similar way to xml submission
    def initialize(response: nil, files: nil)
      @response = response
      # TODO: what is awaiting_media for?
      @raw_odk_xml = files.delete(:xml_submission_file).read
      @files = files
      @response.source = "odk"
    end

    def populate_response
      build_answers(raw_odk_xml)
    end

    private

    def build_answers(raw_odk_xml)
      data = Nokogiri::XML(raw_odk_xml).root
      lookup_and_check_form(id: data["id"], version: data["version"])
      check_for_existing_response
      # TODO: handle awaiting_media

      # Response mission should already be set - TODO: consider moving to constructor or lookup_and_check_form
      raise "Submissions must have a mission" if response.mission.nil?
      build_answer_tree(data, response.form)
    end

    def build_answer_tree(data, form)
      response.root_node = AnswerGroup.new(
        questioning_id: response.form.root_id,
        response_id: response.id
      )
      add_level(data, form, response.root_node)
      puts "********end of build_answer_tree:**********"
      puts response.root_node.debug_tree
    end

    def add_level(xml_node, form_node, response_node)
      puts "Add level: #{xml_node.name}, content: #{xml_node.content}"
      xml_node.elements.each_with_index do |child, index|
        name = child.name
        content = child.content
        unless node_is_odk_header(child)
          puts "Add sibling: #{name} #{content}"
          form_item = form_item(name)
          if form_item.class == QingGroup && form_item.repeatable?
            add_repeat_group(child, form_item, response_node)
          elsif form_item.class == QingGroup
            add_group(child, form_item, response_node)
          elsif form_item.multilevel?
            add_answer_set_member(child, form_item, response_node)
          else
            add_answer(content, form_item, response_node)
          end
        end
      end
    end

    def add_answer_set_member(xml_node, form_item, parent)
      answer_set = find_or_create_answer_set(form_item, parent)
      add_answer(xml_node.content, form_item, answer_set)
    end

    def find_or_create_answer_set(form_item, parent)
      answer_set = parent.c.find do |c|
        c.questioning_id == form_item.id && c.class == AnswerSet
      end
      if answer_set.nil?
        answer_set = AnswerSet.new(questioning_id: form_item.id, new_rank: parent.children.length)
        parent.children << answer_set
      end
      answer_set
    end

    def add_repeat_group(xml_node, form_item, parent)
      group_set = find_or_create_group_set(form_item, parent)
      add_group(xml_node, form_item, group_set)
    end

    def find_or_create_group_set(form_item, parent)
      group_set = parent.c.find do |c|
        c.questioning_id == form_item.id && c.class == AnswerGroupSet
      end
      if group_set.nil?
        group_set = AnswerGroupSet.new(questioning_id: form_item.id, new_rank: parent.children.length)
        parent.children << group_set
      end
      group_set
    end

    def add_group(xml_node, form_item, parent)
      puts "add group: #{xml_node.name}, content: #{xml_node.content}, rank: #{parent.children.length}"
      unless node_is_odk_header(xml_node)
        group = AnswerGroup.new(
          questioning_id: form_item.id,
          new_rank: parent.children.length
        )
        parent.children << group
        add_level(xml_node, form_item, group)
      end
    end

    def add_answer(content, form_item, parent)
      answer = Answer.new(
        questioning_id: form_item.id,
        #value: content,
        new_rank: parent.children.length
      )

      populate_answer_value(answer, content, form_item)
      parent.children << answer
      puts "added answer: #{answer.casted_value}"
    end

    def node_is_odk_header(node)
      /\S*header/.match(node.name).present?
    end

    # finds the appropriate Option instance for an ODK submission
    def option_id_for_submission(option_node_str)
      puts option_node_str
      if option_node_str =~ /\Aon([\w\-]+)\z/
        # look up inputs of the form "on####" as option node ids
        node_id = option_node_str.remove("on")
        OptionNode.id_to_option_id(node_id)
      else
        #TODO: test and failure mode?
        # look up other inputs as option ids
        Option.where(id: option_node_str).pluck(:id).first
      end
    end

    def populate_answer_value(answer, content, form_item)
      case form_item.qtype.name<commit_msg>6441: consolidate making new nodes<commit_after>module Odk
  # Takes a response and odk data. Parses odk data into answer tree for response.
  class ResponseParser
    attr_accessor :response, :raw_odk_xml

    #initialize in a similar way to xml submission
    def initialize(response: nil, files: nil)
      @response = response
      # TODO: what is awaiting_media for?
      @raw_odk_xml = files.delete(:xml_submission_file).read
      @files = files
      @response.source = "odk"
    end

    def populate_response
      build_answers(raw_odk_xml)
    end

    private

    def build_answers(raw_odk_xml)
      data = Nokogiri::XML(raw_odk_xml).root
      lookup_and_check_form(id: data["id"], version: data["version"])
      check_for_existing_response
      # TODO: handle awaiting_media

      # Response mission should already be set - TODO: consider moving to constructor or lookup_and_check_form
      raise "Submissions must have a mission" if response.mission.nil?
      build_answer_tree(data, response.form)
    end

    def build_answer_tree(data, form)
      response.root_node = AnswerGroup.new(
        questioning_id: response.form.root_id,
        response_id: response.id,
        new_rank: 0
      )
      add_level(data, form, response.root_node)
      puts response.root_node.debug_tree
    end

    def add_level(xml_node, form_node, response_node)
      xml_node.elements.each_with_index do |child, index|
        name = child.name
        content = child.content
        unless node_is_odk_header(child)
          form_item = form_item(name)
          if form_item.class == QingGroup && form_item.repeatable?
            add_repeat_group(child, form_item, response_node)
          elsif form_item.class == QingGroup
            add_group(child, form_item, response_node)
          elsif form_item.multilevel?
            add_answer_set_member(child, form_item, response_node)
          else
            add_answer(content, form_item, response_node)
          end
        end
      end
    end

    def make_node(type, form_item, parent)
      type.new(questioning_id: form_item.id, new_rank: parent.children.length, response_id: response.id)
    end

    def add_answer_set_member(xml_node, form_item, parent)
      answer_set = find_or_create_answer_set(form_item, parent)
      add_answer(xml_node.content, form_item, answer_set)
    end

    def find_or_create_answer_set(form_item, parent)
      answer_set = parent.c.find do |c|
        c.questioning_id == form_item.id && c.class == AnswerSet
      end
      if answer_set.nil?
        answer_set = make_node(AnswerSet, form_item, parent)
        parent.children << answer_set
      end
      answer_set
    end

    def add_repeat_group(xml_node, form_item, parent)
      group_set = find_or_create_group_set(form_item, parent)
      add_group(xml_node, form_item, group_set)
    end

    def find_or_create_group_set(form_item, parent)
      group_set = parent.c.find do |c|
        c.questioning_id == form_item.id && c.class == AnswerGroupSet
      end
      if group_set.nil?
        group_set = make_node(AnswerGroupSet, form_item, parent)
        parent.children << group_set
      end
      group_set
    end

    def add_group(xml_node, form_item, parent)
      unless node_is_odk_header(xml_node)
        group = make_node(AnswerGroup, form_item, parent)
        parent.children << group
        add_level(xml_node, form_item, group)
      end
    end

    def add_answer(content, form_item, parent)
      answer = make_node(Answer, form_item, parent)
      populate_answer_value(answer, content, form_item)
      parent.children << answer
    end

    def node_is_odk_header(node)
      /\S*header/.match(node.name).present?
    end

    # finds the appropriate Option instance for an ODK submission
    def option_id_for_submission(option_node_str)
      if option_node_str =~ /\Aon([\w\-]+)\z/
        # look up inputs of the form "on####" as option node ids
        node_id = option_node_str.remove("on")
        OptionNode.id_to_option_id(node_id)
      else
        #TODO: test and failure mode?
        # look up other inputs as option ids
        Option.where(id: option_node_str).pluck(:id).first
      end
    end

    def populate_answer_value(answer, content, form_item)
      case form_item.qtype.name
{'fraction_non_alphanumeric': 0.08236208236208237, 'fraction_numerical': 0.000555000555000555, 'mean_word_length': 2.803292528493035, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 36}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1764006', 'n_tokens_mistral': 2867, 'n_tokens_neox': 2810, 'n_words': 653}