# !/usr/bin/env python3
# Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

r"""
The source file of the functions for single-qubit quantum gates.
"""

import math
import paddle
import paddle_quantum
from .base import simulation
from paddle_quantum.intrinsic import _get_float_dtype, _zero, _one


def h(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply a Hadamard gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    element = 1 / math.sqrt(2)
    gate = [
        [element, element],
        [element, -element],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def s(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply an S gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        [1, 0],
        [0, 1j],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def sdg(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply an S dagger (inverse S) gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
          Output state.
    """
    gate = [
        [1, 0],
        [0, -1j],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def t(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply a T gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        [1, 0],
        [0, math.cos(math.pi / 4) + math.sin(math.pi / 4) * 1j],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state

def tdg(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply a T dagger (inverse T) gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        [1, 0],
        [0, math.cos(math.pi/4) - math.sin(math.pi/4) * 1j],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state

def x(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply an X gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        [0, 1],
        [1, 0],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def y(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply a Y gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        [0, -1j],
        [1j, 0],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def z(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
    r"""Apply a Z gate on the input state.

    Args:
        state: Input state.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        [1, 0],
        [0, -1],
    ]
    gate = paddle.to_tensor(gate, dtype=dtype)
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def p(
        state: paddle_quantum.State, theta: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State:
    r"""Apply a P gate on the input state.

    Args:
        state: Input state.
        theta: Parameter of the gate.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        _one(dtype), _zero(dtype),
        _zero(dtype), paddle.cos(theta).cast(dtype) + 1j * paddle.sin(theta).cast(dtype),
    ]
    gate = paddle.reshape(paddle.concat(gate), [2, 2])
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def rx(
        state: paddle_quantum.State, theta: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State:
    r"""Apply a single-qubit rotation gate about the x-axis on the input state.

    Args:
        state: Input state.
        theta: Parameter of the gate.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        paddle.cos(theta / 2).cast(dtype), -1j * paddle.sin(theta / 2).cast(dtype),
        -1j * paddle.sin(theta / 2).cast(dtype), paddle.cos(theta / 2).cast(dtype),
    ]
    gate = paddle.reshape(paddle.concat(gate), [2, 2])
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def ry(
        state: paddle_quantum.State, theta: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State:
    r"""Apply a single-qubit rotation gate about the y-axis on the input state.

    Args:
        state: Input state.
        theta: Parameter of the gate.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        paddle.cos(theta / 2).cast(dtype), (-paddle.sin(theta / 2)).cast(dtype),
        paddle.sin(theta / 2).cast(dtype), paddle.cos(theta / 2).cast(dtype),
    ]
    gate = paddle.reshape(paddle.concat(gate), [2, 2])
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def rz(
        state: paddle_quantum.State, theta: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State:
    r"""Apply a single-qubit rotation gate about the z-axis on the input state.

    Args:
        state: Input state.
        theta: Parameter of the gate.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    gate = [
        paddle.cos(theta / 2).cast(dtype) - 1j * paddle.sin(theta / 2).cast(dtype), _zero(dtype),
        _zero(dtype), paddle.cos(theta / 2).cast(dtype) + 1j * paddle.sin(theta / 2).cast(dtype),
    ]
    gate = paddle.reshape(paddle.concat(gate), [2, 2])
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state


def u3(
        state: paddle_quantum.State, theta: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State:
    r"""Apply a single-qubit rotation gate on the input state.

    Args:
        state: Input state.
        theta: Parameters of the gate.
        qubit_idx: Index of the qubit on which the gate is applied.
        dtype: Type of data.
        backend: Backend on which the simulation is run.

    Returns:
        Output state.
    """
    # theta is theta[0], phi is theta[1], lambda is theta[2]
    gate_real = [
        paddle.cos(theta[0] / 2),
        -paddle.cos(theta[2]) * paddle.sin(theta[0] / 2),
        paddle.cos(theta[1]) * paddle.sin(theta[0] / 2),
        paddle.cos(theta[1] + theta[2]) * paddle.cos(theta[0] / 2),
    ]
    gate_real = paddle.reshape(paddle.concat(gate_real), [2, 2])

    gate_imag = [
        paddle.to_tensor(0, dtype=_get_float_dtype(dtype)),
        -paddle.sin(theta[2]) * paddle.sin(theta[0] / 2),
        paddle.sin(theta[1]) * paddle.sin(theta[0] / 2),
        paddle.sin(theta[1] + theta[2]) * paddle.cos(theta[0] / 2),
    ]
    gate_imag = paddle.reshape(paddle.concat(gate_imag), [2, 2])

    gate = gate_real + 1j * gate_imag
    state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
    transformed_state = state.clone()
    transformed_state.data = state_data
    return transformed_state
