""" Search strategy planner

This script is the entry point of the search strategy planning.

* Calls the correct search strategy function so it can be thought of
  as a dispatcher.

Public functions: (Outdated)
    * get_search_plan - returns search plan generated by the specified algorithm
    * get_fuzzing_stats - stats about number of scenarios at each depth
"""
from scipy.special import comb
from fuzzer.common.FuzzData import FuzzData
from fuzzer.strategies import baseline_strategies as base
from fuzzer.strategies import heuristic_strategy as heuristic
from fuzzer.strategies import neighbor_heuristic as nh


class Statespace:
    def __init__(self, depth: int, links: list):
        self.depth = depth
        self.links = links
        self.verify_input()

    def get_bfs_plan(self) -> list:
        return base.bfs(self.depth, self.links)

    def get_dfs_plan(self) -> list:
        return base.dfs(self.depth, self.links)

    def get_heuristic_plan(self, props: dict, fib, fuzz_data: FuzzData) -> list:
        return heuristic.heuristic(self.depth, self.links, props, fib, fuzz_data)

    def get_neighbor_heuristic_plan(self, properties: dict, fuzz_data: FuzzData):
        return nh.heuristic(self.depth, properties, fuzz_data)

    # @Tested
    def get_fuzzing_stats(self) -> dict:
        """ Get info about the possible failures for each depth """
        stats = {}
        total = 0
        N = len(self.links)

        for k in range(0, self.depth + 1):
            num_combs = comb(N, k, exact=True)
            depth_key = "depth{}".format(k)
            stats.update({depth_key: num_combs})
            total += num_combs

        stats.update({"total": total})

        return stats

    # @Tested
    def verify_input(self):
        """ Function that collects all checks on the class input """
        verify_depth(self.depth, self.links)


# @Tested composite
def verify_depth(depth: int, links: list):
    """ Verify depth is not greater than the number of elements in the list
    In that case the underlying combinations algorithm would just give
    all combinations as if (depth == len(nets)) but we choose to raise
    an exception as this is likely indicative of an error somewhere
    """
    if depth > len(links):
        raise ValueError("Depth is higher than number of links")
