# This code is part of Qiskit.
#
# (C) Copyright IBM 2019, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Adds q^T * w to separate register for non-negative integer weights w."""

import logging

import warnings
import numpy as np

from qiskit.circuit.library.arithmetic import WeightedAdder
from qiskit.aqua import AquaError
from qiskit.aqua.utils.circuit_factory import CircuitFactory

logger = logging.getLogger(__name__)


class WeightedSumOperator(CircuitFactory):
    """Adds q^T * w to separate register for non-negative integer weights w."""

    def __init__(self, num_state_qubits, weights, i_state=None, i_sum=None):
        """Computes the weighted sum controlled by state qubits

        Args:
            num_state_qubits (int): number of state qubits
            weights (Union(list, numpy.ndarray)): weights per state qubits
            i_state (Optional(Union(list, numpy.ndarray))): indices of state qubits,
                                    set to range(num_state_qubits) if None
            i_sum (Optional(int)): indices of target qubits (that represent the resulting sum),
                set to range(num_state_qubits, num_state_qubits + req_num_sum_qubits) if None
        Raises:
            AquaError: invalid input
        """
        warnings.warn('The WeightedSumOperator class is deprecated as of Aqua 0.8.0 and will be '
                      'removed no sooner than 3 months after that release. You should use the '
                      'qiskit.circuit.library.WeightedAdder instead.',
                      DeprecationWarning, stacklevel=2)

        self._weights = weights

        # check weights
        for i, w in enumerate(weights):
            if not np.isclose(w, np.round(w)):
                logger.warning('Non-integer weights are rounded to '
                               'the nearest integer! (%s, %s).', i, w)

        self._num_state_qubits = num_state_qubits
        self._num_sum_qubits = self.get_required_sum_qubits(weights)
        self._num_carry_qubits = self.num_sum_qubits - 1

        num_target_qubits = num_state_qubits + self.num_sum_qubits
        super().__init__(num_target_qubits)

        if i_state is None:
            self.i_state = list(range(num_state_qubits))
        else:
            self.i_state = i_state
        if i_sum is None:
            self.i_sum = \
                list(range(max(self.i_state) + 1, max(self.i_state) + self.num_sum_qubits + 1))
        else:
            if len(i_sum) == self.get_required_sum_qubits(weights):
                self.i_sum = i_sum
            else:
                raise AquaError('Invalid number of sum qubits {}! Required {}'.format(
                    len(i_sum), self.get_required_sum_qubits(weights)
                ))

    @staticmethod
    def get_required_sum_qubits(weights):
        """ get required sum qubits """
        return int(np.floor(np.log2(sum(weights))) + 1)

    @property
    def weights(self):
        """ returns weights """
        return self._weights

    @property
    def num_state_qubits(self):
        """ returns num state qubits """
        return self._num_state_qubits

    @property
    def num_sum_qubits(self):
        """ returns num sum qubits """
        return self._num_sum_qubits

    @property
    def num_carry_qubits(self):
        """ returns num carry qubits """
        return self._num_carry_qubits

    def required_ancillas(self):
        """ required ancillas """
        if self.num_sum_qubits > 2:
            # includes one ancilla qubit for 3-controlled not gates
            # TODO: validate when the +1 is needed and make a case distinction
            return self.num_carry_qubits + 1
        else:
            return self.num_carry_qubits

    def required_ancillas_controlled(self):
        """ returns required ancillas controlled """
        return self.required_ancillas()

    def build(self, qc, q, q_ancillas=None, params=None):
        instr = WeightedAdder(num_state_qubits=self.num_state_qubits,
                              weights=self.weights).to_instruction()
        qr = [q[i] for i in self.i_state + self.i_sum]
        if q_ancillas:
            qr += q_ancillas[:self.required_ancillas()]  # pylint:disable=unnecessary-comprehension
        qc.append(instr, qr)
