import dimod

from dwave_qbsolv.qbsolv_binding import run_qbsolv, ENERGY_IMPACT, SOLUTION_DIVERSITY

__all__ = ['QBSolv', 'ENERGY_IMPACT', 'SOLUTION_DIVERSITY']


class QBSolv(dimod.TemplateSampler):
    """Wraps the qbsolv C package for python.

    Examples:
        >>> h = {0: -1, 1: 1, 2: -1}
        >>> J = {(0, 1): -1, (1, 2): -1}
        >>> response = QBSolv().sample_ising(h, J)
        >>> list(response.samples())
        '[{0: 1, 1: 1, 2: 1}]'
        >>> list(response.energies())
        '[1.0]'
    """

    @dimod.decorators.qubo(1)
    @dimod.decorators.qubo_index_labels(1)
    def sample_qubo(self, Q, num_repeats=50, seed=None, algorithm=None,
                    verbosity=-1, timeout=2592000, solver_limit=None, solver=None,
                    target=None, find_max=False, **sample_kwargs):
        """Sample low-energy states defined by a QUBO using qbsolv.

        Note:
            The qbsolv library being shared by all instances of this class is
            non-rentrant and not thread safe. The GIL should not be released
            by this method until that is resolved.

        Note:
            The default build of this library doesn't have the dw library,
            to use solver='dw' this module must be built from source with
            that library.

        The parameter solver given to this method has several valid forms:
            - (default) The string 'tabu': sub problems will be called via an internal call to tabu.
            - The string 'dw': sub problems will be given to the dw library.
            - an instance of a dimod sampler, the sample_qubo method will be invoked.
            - an callable that has the signature (qubo: dict, current_best: dict)
                and returns a result list/dictionary with the new solution

        Args:
            Q (dict): A dictionary defining the QUBO. Should be of the form
                {(u, v): bias} where u, v are variables and bias is numeric.
            num_repeats (int, optional): Determines the number of times to
                repeat the main loop in qbsolv after determining a better
                sample. Default 50.
            seed (int, optional): Random seed. Default generated by random module.
            algorithm (int, optional): Which algorithm to use. Defaut
                ENERGY_IMPACT. Algorithms numbers can be imported from module
                under the names ENERGY_IMPACT and SOLUTION_DIVERSITY.
            verbosity (int, optional): Print more detail about qbsolv's internal
                process as this number gets higher.
            timeout (float, optional): Number of seconds before routine halts. Default: 2592000
            solver: The sampling method for qbsolv to use, see method description.
            solver_limit (int, optional): The maximum number of variables in a sub problem.
            target (float, optional): If given the qbsolv process haults when
                a state with this energy value or better is discoverd. (default None)
            find_max (bool, optional): Switches the process from minimization to
                maximization. (default False, minimization)

        Returns:
            :obj:`BinaryResponse`

        Examples:
            >>> Q = {(0, 0): 1, (1, 1): 1, (0, 1): 1}
            >>> response = QBSolv().sample_qubo(Q)
            >>> list(response.samples())
            '[{0: 0, 1: 0}]'
            >>> list(response.energies())
            '[0.0]'

        """
        if not isinstance(num_repeats, int) or num_repeats <= 0:
            raise ValueError("num_repeats must be a positive integer")

        # pose the QUBO to qbsolv
        samples, counts = run_qbsolv(Q=Q, num_repeats=num_repeats, seed=seed, algorithm=algorithm,
                                     verbosity=verbosity, timeout=timeout, solver_limit=solver_limit,
                                     solver=solver, target=target, find_max=find_max, sample_kwargs=sample_kwargs)

        # load the response
        response = dimod.BinaryResponse()
        response.add_samples_from(samples, sample_data=({'count': n} for n in counts), Q=Q)

        return response
