"""Convenience Module for creating different c3_backend
"""
from typing import List
import tensorflow as tf
import math
from .c3_exceptions import C3QiskitError

GATE_MAP = {
    "X": "RXp",
    "x": "RXp",
    "Y": "RYp",
    "y": "RYp",
    "Z": "RZp",
    "z": "RZp",
    "CNOT": "CRXp",
    "CX": "CRXp",
    "cx": "CRXp",
    "CZ": "CRZp",
    "cz": "CRZp",
    "I": "Id",
    "u0": "Id",
    "id": "Id",
    "RX90p": "RX90p",  # RX((np.pi)/2)
    "RX90m": "RX90m",  # RX((-np.pi)/2)
    "RY90p": "RY90p",  # RY((np.pi)/2)
    "RY90m": "RY90m",  # RY(-(np.pi)/2)
    "RZ90p": "RZ90p",  # RZ((np.pi)/2)
    "RZ90m": "RZ90m",  # RZ(-(np.pi)/2)
    "iSwap": "ISWAP",
}


def pad_gate_name(gate_name: str, qubits: List[int], n_qubits: int) -> str:
    """Pad gate name with Identity gates in correct indices

    Parameters
    ----------
    gate_name : str
        A C3 compatible gate name
    qubits : List[int]
        Indices to apply gate
    n_qubits : int
        Total number of qubits in the device

    Returns
    -------
    str
        Identity padded gate name, eg ::

            pad_gate_name("CCRX90p", [1, 2, 3], 5) -> 'Id:CCRX90p:Id'
            pad_gate_name("RX90p", [0], 5) -> 'RX90p:Id:Id:Id:Id'
    """

    # TODO (check) Assumption control and action qubits next to each other
    gate_names = ["Id"] * (n_qubits - (len(qubits) - 1))
    gate_names[qubits[0]] = gate_name
    padded_gate_str = ":".join(gate_names)
    return padded_gate_str


def get_sequence(instructions: List, n_qubits: int) -> List[str]:
    """Return a sequence of gates from instructions

    Parameters
    ----------
    instructions : List[dict]
        Instructions from the qasm experiment, for example::

        instructions: [
                {"name": "u1", "qubits": [0], "params": [0.4]},
                {"name": "u2", "qubits": [0], "params": [0.4,0.2]},
                {"name": "u3", "qubits": [0], "params": [0.4,0.2,-0.3]},
                {"name": "snapshot", "label": "snapstate1", "snapshot_type": "statevector"},
                {"name": "cx", "qubits": [0,1]},
                {"name": "barrier", "qubits": [0]},
                {"name": "measure", "qubits": [0], "register": [1], "memory": [0]},
                {"name": "u2", "qubits": [0], "params": [0.4,0.2], "conditional": 2}
            ]

    n_qubits: int
        Number of qubits in the device config

    Returns
    -------
    List[str]
        List of gates, for example::

        sequence = ["RX90p:Id", "Id:RX90p", "CR90"]

    """

    sequence = []

    for instruction in instructions:

        # TODO Check if gate is possible from device_config
        # TODO parametric gates

        iname = instruction.name
        # Conditional operations are not supported
        conditional = getattr(instructions, "conditional", None)  # noqa
        if conditional is not None:
            raise C3QiskitError("C3 Simulator does not support conditional operations")

        # reset, binary functions is not supported
        elif iname in ["reset", "bfunc"]:
            raise C3QiskitError("C3 Simulator does not support {}".format(iname))

        # barrier is implemented internally through Identity gates
        elif iname == "barrier":
            pass

        # TODO U, u3
        elif iname in ("U", "u3"):
            raise C3QiskitError("U3 gates are not yet implemented")

        # measure implemented outside sequences
        elif iname == "measure":
            pass

        elif iname in GATE_MAP.keys():
            gate_name = GATE_MAP[iname]
            qubits = instruction.qubits
            padded_gate_str = pad_gate_name(gate_name, qubits, n_qubits)
            sequence.append(padded_gate_str)

        # raise C3QiskitError if unknown instruction
        else:
            raise C3QiskitError("Encountered unknown operation {}".format(iname))

    return sequence


def get_init_ground_state(n_qubits: int, n_levels: int) -> tf.Tensor:
    """Return a perfect ground state

    Parameters
    ----------
    n_qubits : int
        Number of qubits in the system

    n_levels : int
        Number of levels for each qubit

    Returns
    -------
    tf.Tensor
        Tensor array of ground state
        shape(m^n, 1), dtype=complex128
        m = no of qubit levels
        n = no of qubits
    """
    psi_init = [[0] * (int)(math.pow(n_levels, n_qubits))]
    psi_init[0][0] = 1
    init_state = tf.transpose(tf.constant(psi_init, tf.complex128))

    return init_state
