# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License.  See the LICENSE file in the package base
# directory for details.

if greedy_search:
        # greedy search for best suggestion configuration:
        # for all hotspot types in descending importance:
        for hotspot_type in [HotspotType.YES, HotspotType.MAYBE, HotspotType.NO]:
            if hotspot_information:
                # hotspot information exists
                if hotspot_type not in hotspot_information:
                    continue
                # for all loops in descending order by average execution time
                loop_tuples = hotspot_information[hotspot_type]
                sorted_loop_tuples = sorted(loop_tuples, key=lambda x: x[4], reverse=True)
            else:
                # no hotspot information was found
                # get loop tuples from detection result
                loop_nodes = all_nodes(detection_result.pet, type=LoopNode)
                loop_tuples = [(l.file_id, l.start_line, HotspotNodeType.LOOP, "", 0.0) for l in loop_nodes]

            sorted_loop_tuples = sorted(loop_tuples, key=lambda x: x[4], reverse=True)

            for loop_tuple in sorted_loop_tuples:
                loop_str = (
                    ""
                    + str(loop_tuple[0])
                    + "@"
                    + str(loop_tuple[1])
                    + " - "
                    + str(loop_tuple[2])
                    + " "
                    + loop_tuple[3]
                    + " "
                    + str(round(loop_tuple[4], 3))
                    + "s"
                )
                # check if the loop contributes more than 1% to the total runtime, if hotspot information exists
                loop_contributes_significantly = (loop_tuple[4] > (max_avg_runtime / 100)) or not hotspot_information
                if not loop_contributes_significantly:
                    statistics_graph.add_child(loop_str, color=NodeColor.ORANGE)
                else:
                    statistics_graph.add_child(loop_str)
                statistics_graph.update_current_node(loop_str)
                # identify all applicable suggestions for this loop
                logger.debug(str(hotspot_type) + " loop: " + str(loop_tuple))
                if not loop_contributes_significantly:
                    logger.debug("--> Skipping loop due to runtime contribution < 1%")
                    continue
                # create code and execute for all applicable suggestions
                applicable_suggestions = get_applicable_suggestion_ids(loop_tuple[0], loop_tuple[1], detection_result)
                logger.debug("--> applicable suggestions: " + str(applicable_suggestions))
                suggestion_effects: List[Tuple[List[SUGGESTION_ID], CodeConfiguration]] = []
                for suggestion_id in applicable_suggestions:
                    current_config = best_suggestion_configuration[0] + [suggestion_id]
                    if current_config in visited_configurations:
                        continue
                    visited_configurations.append(current_config)
                    tmp_config = reference_configuration.create_copy(
                        arguments, "par_settings.json", get_unique_configuration_id
                    )
                    tmp_config.apply_suggestions(arguments, current_config)
                    tmp_config.execute(arguments, timeout=timeout_after)
                    statistics_graph.add_child(
                        "step "
                        + str(statistics_step_num)
                        + "\n"
                        + str(current_config)
                        + "\n"
                        + tmp_config.get_statistics_graph_label(),
                        shape=NodeShape.BOX,
                        color=tmp_config.get_statistics_graph_color(),
                    )
                    # only consider valid code
                    debug_stats.append(
                        (
                            current_config,
                            cast(ExecutionResult, tmp_config.execution_result).runtime,
                            cast(ExecutionResult, tmp_config.execution_result).return_code,
                            cast(ExecutionResult, tmp_config.execution_result).result_valid,
                            cast(ExecutionResult, tmp_config.execution_result).thread_sanitizer,
                            tmp_config.root_path,
                        )
                    )
                    if (
                        cast(ExecutionResult, tmp_config.execution_result).result_valid
                        and cast(ExecutionResult, tmp_config.execution_result).return_code == 0
                    ):
                        suggestion_effects.append((current_config, tmp_config))
                    else:
                        if arguments.skip_cleanup:
                            continue
                        # delete invalid code
                        tmp_config.deleteFolder()
                # add current best configuration for reference / to detect "no suggestions is beneficial"
                suggestion_effects.append(best_suggestion_configuration)
                statistics_graph.add_child(
                    "step "
                    + str(statistics_step_num)
                    + "\n"
                    + str(best_suggestion_configuration[0])
                    + "\n"
                    + best_suggestion_configuration[1].get_statistics_graph_label(),
                    shape=NodeShape.BOX,
                    color=best_suggestion_configuration[1].get_statistics_graph_color(),
                )

                logger.debug(
                    "Suggestion effects:\n" + str([(str(t[0]), str(t[1].execution_result)) for t in suggestion_effects])
                )

                # select the best option and save it in the current best_configuration
                sorted_suggestion_effects = sorted(
                    suggestion_effects, key=lambda x: cast(ExecutionResult, x[1].execution_result).runtime
                )
                buffer = sorted_suggestion_effects[0]
                best_suggestion_configuration = buffer
                sorted_suggestion_effects = sorted_suggestion_effects[1:]  # in preparation of cleanup step
                logger.debug(
                    "Current best configuration: "
                    + str(best_suggestion_configuration[0])
                    + " stored at "
                    + best_suggestion_configuration[1].root_path
                )
                # update the timeout according to the new time measurement
                timeout_after = max(
                    3.0, cast(ExecutionResult, best_suggestion_configuration[1].execution_result).runtime * 2
                )
                logger.debug("Updated timeout to: " + str(round(timeout_after, 3)))

                # update the graph and store the current best configuration
                statistics_graph.add_child(
                    "step "
                    + str(statistics_step_num)
                    + "\n"
                    + str(best_suggestion_configuration[0])
                    + "\n"
                    + best_suggestion_configuration[1].get_statistics_graph_label(),
                    shape=NodeShape.BOX,
                    color=best_suggestion_configuration[1].get_statistics_graph_color(),
                )
                statistics_graph.update_current_node(
                    "step "
                    + str(statistics_step_num)
                    + "\n"
                    + str(best_suggestion_configuration[0])
                    + "\n"
                    + best_suggestion_configuration[1].get_statistics_graph_label()
                )
                statistics_graph.output()
                statistics_step_num += 1
                # cleanup other configurations (excluding original version)
                if not arguments.skip_cleanup:
                    logger.debug("Cleanup:")
                    for _, config in sorted_suggestion_effects:
                        if config.root_path == reference_configuration.root_path:
                            continue
                        config.deleteFolder()

                # continue with the next loop
