/* Copyright 2020, 2021 Evandro Chagas Ribeiro da Rosa <evandro.crr@posgrad.ufsc.br>
 * Copyright 2020, 2021 Rafael de Santiago <r.santiago@ufsc.br>
 *
 * 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.
 */

#pragma once
#include <complex>
#include <iostream> // std::cout needs to be initialized before ket::ket_init_new
#include <map>
#include <memory>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <vector>

//! Main libket namespace.
namespace ket {

    /*! \brief KQASM metrics.
     * 
     *  \class metrics
     * 
     *  \note 
     *      Use the report function to get current process metrics.
     */
    class metrics {
    public:
        metrics();
        metrics(size_t used_qubits, size_t free_qubits, size_t allocated_qubits,
                size_t max_allocated_qubits, size_t measurements, 
                std::unordered_map<std::string, size_t> gates, size_t gates_sum, 
                std::unordered_map<size_t, size_t> ctrl_gates, size_t ctrl_gates_sum, 
                std::unordered_map<std::string, size_t> plugins, size_t plugins_sum);

        //! \brief Cast the metrics to a string.
        std::string str() const;
        std::string __str__() const;

        //! \brief Get the number of qubits used.
        size_t get_qubits_used();
        
        //! \brief Get the number of free qubits.
        size_t get_free_qubits();
        
        //! \brief Get the number of allocated qubits.
        size_t get_allocated_qubits();
        
        //! \brief Get the number of max allocated qubits concurrently.
        size_t get_max_allocated_qubits();
        
        //! \brief Get the number of measurements.
        size_t get_measurements();
        
        //! \brief Get the number of a specific quantum gates.
        size_t get_gates(std::string gate);
        
        //! \brief Get the number of quantum gates.
        size_t get_gates_sum();
        
        //! \brief Get the number of quantum CTRL gates with a specific number of control.
        size_t get_ctrl_gates(size_t number);
        
        //! \brief Get the number of quantum CTRL gates.
        size_t get_ctrl_gates_sum();
        
        //! \brief Get the number of used of a plugins.
        size_t get_plugins(std::string name);
        
        //! \brief Get the number of plugins used.
        size_t get_plugins_sum();

    private:
        size_t used_qubits;
        size_t free_qubits;
        size_t allocated_qubits;
        size_t max_allocated_qubits;
        size_t measurements;
        std::unordered_map<std::string, size_t> gates;
        size_t gates_sum;
        std::unordered_map<size_t, size_t> ctrl_gates;
        size_t ctrl_gates_sum;
        std::unordered_map<std::string, size_t> plugins;
        size_t plugins_sum;        
    };

    #ifndef SWIG
    /*! \brief KQASM manager.

     *  \class process
     *  \note
     *      This class should not be directly instanced or manipulated.
     */
    class process {
    public:
    
        enum Gate {X, Y,  Z,  H,
                   S, SD, T,  TD, 
                   phase, RX, RY, RZ}; //!< Available quantum gates. 

        /*! \brief Contructor.
         *  
         *  \note
         *      Use ket::process_begin and ket::process_end to create and
         *      destroy process instances.
         */
        process();

        /*! \brief Execute the generated KQASM.
         *  
         *  Also, place the results in the future and dump instancies.
         *  
         *  \note
         *      Use ket::future.get or ket::dump.get trigger the quantum execution.
         */
        void exec();

        /*! \brief Add a instruction (not quantum gate).
         *  
         *  \throw
         *      std::runtime_error inside ctrl or adj scope.
         * 
         *  \warning
         *      Place undefined instruction breaks the quantum execution.
         */
        void add_inst(const std::string& inst);

        /*! \brief Add a Label.
         *  
         *  \throw
         *      std::runtime_error inside ctrl or adj scope.
         * 
         *  \warning
         *      Place undefined instruction breaks the quantum execution.
         */
        void add_label(const std::string& label);


        //! \brief Add a quantum gate.
        void add_gate(Gate gate, size_t qubit, double arg = NAN);
        
        //! \brief Add a Ket Bitwise plugin.
        void add_plugin(const std::string&         name, 
                        const std::vector<size_t>& qubits,
                        const std::string&         args);

        /*! \brief Alloc qubits.
         *  
         *  \note
         *      Use the ket::quant constructors to allocate qubits.
         */
        std::vector<size_t> quant(size_t size, bool dirty);

        /*! \brief Measure qubits
         *
         *  \note
         *      Use ket::measure to measure a ket::quant.
         */
        std::tuple<size_t, std::shared_ptr<std::int64_t>, std::shared_ptr<bool>>
        measure(const std::vector<size_t>& qubits);  

        /*! \brief Initialize an int.
         *
         *  \note
         *      Use ket::future to instance an int in the quantum computer.
         */
        std::tuple<size_t, std::shared_ptr<std::int64_t>, std::shared_ptr<bool>>
        new_int(std::int64_t value);

        //! \brief Add a operation between int.
        std::tuple<size_t, std::shared_ptr<std::int64_t>, std::shared_ptr<bool>>
        op_int(size_t left, const std::string& op, size_t right);
        
        //! \brief Get a dump of the qubits.
        std::tuple<size_t, std::shared_ptr<std::map<std::vector<std::uint64_t>, std::vector<std::complex<double>>>>, std::shared_ptr<bool>>
        dump(const std::vector<size_t>& qubits);
       
        /*! \brief Initialize an inverse scope.
         *
         * \note
         *      Use ket::adj_begin to initialize an adj scope.
         */
        void adj_begin();
        
        /*! \brief End the inverse scope.
         *
         * \note
         *      Use ket::adj_end to end an adj scope.
         */
        void adj_end();

        /*! \brief Initialize a controlled scope.
         *
         * \note
         *      Use ket::ctrl_begin to initialize an ctrl scope.
         */
        void ctrl_begin(const std::vector<size_t>& control);
        
        /*! \brief End the controlled scope.
         *
         * \note
         *      Use ket::ctrl_end to end an ctrl scope.
         */
        void ctrl_end();

        /*! \brief Free qubits.
         *  
         *  \note
         *      Use ket::quant.free to free a ket::quant. 
         */ 
        void free(size_t qubit, bool dirty);

        //! \brief Get a new label id.
        size_t new_label_id();
        
        //! \brief See if a qubit is free.
        bool is_free(size_t qubit) const;

        metrics get_metrics() const;

        bool has_executed() const;

        std::string get_result_str() const;

    private:
        size_t qubit_count, future_count, label_count, dump_count; 
        
        std::unordered_map<size_t, std::pair<std::shared_ptr<std::int64_t>, std::shared_ptr<bool>>> measure_map;
        std::unordered_map<size_t, std::pair< std::shared_ptr<std::map<std::vector<std::uint64_t>, std::vector<std::complex<double>>>>, std::shared_ptr<bool>>> dump_map;
       
        std::unordered_set<size_t> qubits_free;
        
        std::stack<std::stack<std::string>> adj_stack; 
        std::vector<std::vector<size_t>> ctrl_stack;  

        std::string kqasm;

        /* Metrics */
        size_t used_qubits;
        size_t free_qubits;
        size_t allocated_qubits;
        size_t max_allocated_qubits;

        size_t measurements;
        size_t n_dumps;

        size_t n_set_inst;

        std::unordered_map<std::string, size_t> gates;
        size_t gates_sum;

        std::unordered_map<size_t, size_t> ctrl_gates;
        size_t ctrl_gates_sum;

        std::unordered_map<std::string, size_t> plugins;
        size_t plugins_sum;

        size_t n_blocks;

        bool executed;      

        std::string result;  
    };

    inline std::stack<std::shared_ptr<process>> process_stack;
    inline std::stack<std::shared_ptr<bool>> process_on_top_stack; 

    inline std::string kbw_addr;
    inline std::string kbw_port;
    inline bool execute_kqasm;
    inline bool dump_to_fs;
    inline bool send_seed;
    
    inline std::string api_args;
    inline std::unordered_map<std::string, std::string> api_args_map;

    inline std::string kqasm_path;
    inline bool output_kqasm;
    extern "C" void ket_init_new(int argc, char* argv[]) __attribute__((constructor));
    #endif

    void config(std::string param, std::string value);

    struct context {

        context();

        bool has_executed() const;
        bool in_scope() const;
        std::string get_return(const std::string& arg) const;
        std::string get_json() const;

        #ifndef SWIG
        const std::shared_ptr<bool> process_on_top;
        const std::shared_ptr<process> ps;
        #endif
    };

    /*! \brief Array of qubits references.
     *  \class quant
     */
    struct quant {
        #ifndef SWIG
        quant(const std::vector<size_t>&      qubits, 
              const std::shared_ptr<bool>&    ps_ot, 
              const std::shared_ptr<process>& ps);
        #endif

        //! \brief Alloc size qubits.
        quant(size_t size);

        /*! \brief Alloc size dirty qubits.
         *  
         *  \warning
         *      Use dirty qubits may have side effects due to previous
         *      entanglement.
         */
        static quant dirty(size_t size);

        #ifndef SWIG
        /*! \brief Return the reference for a single qubit.
         * 
         *  \note
         *      For the Python wrapper use brackets instead.
         */
        quant operator()(int idx) const;

        /*! \brief Return the reference for a range of qubits.
         * 
         *  \note
         *      For the Python wrapper use brackets instead.
         */
        quant operator()(int start, int end, int step = 1) const;
        #endif

        /*! \brief Concatenate two quant.
         * 
         *  \note
         *      The two quant variables needs to belong to the same process. 
         */
        quant operator|(const quant& other) const;

        //! \brief Return a quant with qubits in the inverted order
        quant inverted() const;

        //! \brief Return the number qubits of the quant.
        size_t len() const;
        size_t __len__() const;

        /*! \brief Free the qubits.
         * 
         *  \note 
         *      All qubits must be at the state \f$\left|0\right>\f$ before the call,
         *      otherwise set the dirty param to true.
         * 
         *  \warning 
         *      No check is applied to see if the qubits are at state
         *      \f$\left|0\right>\f$.
         */
        void free(bool dirty = false) const;
        
        //! \brief See if all qubits are free.
        bool is_free() const;

        #ifndef SWIG
        const std::vector<size_t> qubits;
        const std::shared_ptr<bool> process_on_top;
        const std::shared_ptr<process> ps;
        #endif
    };

    /*! \brief Reference to a int available in the quantum computer.
     *  \class future
     */
    class future {
    public:
        #ifndef SWIG
        future(size_t id,
               const std::shared_ptr<std::int64_t>& result,
               const std::shared_ptr<bool>&         available);
        #endif

        //! \brief Initialize a future with a value.
        future(std::int64_t value);

        future operator==(const future& other) const;
        future operator!=(const future& other) const;
        future operator<(const future& other) const;
        future operator<=(const future& other) const;
        future operator>(const future& other) const;
        future operator>=(const future& other) const;

        future operator==(std::int64_t other) const;
        future operator!=(std::int64_t other) const;
        future operator<(std::int64_t  ther) const;
        future operator<=(std::int64_t  other) const;
        future operator>(std::int64_t  ther) const;
        future operator>=(std::int64_t  other) const;

        future operator+(const future& other) const;
        future operator-(const future& other) const;
        future operator*(const future& other) const;
        future operator/(const future& other) const;
        future operator<<(const future& other) const;
        future operator>>(const future& other) const;
        future operator&(const future& other) const;
        future operator^(const future& other) const;
        future operator|(const future& other) const;
        
        future operator+(std::int64_t other) const;
        future operator-(std::int64_t other) const;
        future operator*(std::int64_t other) const;
        future operator/(std::int64_t other) const;
        future operator<<(std::int64_t other) const;
        future operator>>(std::int64_t other) const;
        future operator&(std::int64_t other) const;
        future operator^(std::int64_t other) const;
        future operator|(std::int64_t other) const;

        /*! \brief Get the integer from the quantum computer.
         *  
         *  \note
         *      This method triggers the quantum execution.
         */
        std::int64_t get();

        //! \brief Set the future value with another future.
        void set(const future& other);

        #ifndef SWIG
        size_t get_id() const;
        const std::shared_ptr<bool> process_on_top;
        #endif

    private:
        size_t id;
        std::shared_ptr<std::int64_t> result;
        std::shared_ptr<bool> available;
    };

    /*! \brief Label for a KQASM block.
     *  \class label
     */
    class label {
    public:
        label(const std::string& name);

        //! \brief Place the label in the KQASM file.
        void begin();
         
        #ifndef SWIG
        const std::string name;
        const std::shared_ptr<bool> process_on_top;
        #endif
    private:  
        bool placed;
    };

    //! \brief Stack a new process.
    void process_begin();

    //! \brief Pop a process from the stack.
    void process_end();

    //! \brief Jump to a label in the quantum code. 
    void jump(const label& label_name);

    //! \brief Conditional branch to a label in the quantum code. 
    void branch(const future& cond, const label& label_true, const label& label_false);

    /*! \brief Initialize a controlled scope.
     *  
     *  Every quantum gate inside are controlled by q.
     */ 
    void ctrl_begin(const quant& q);

    //! \brief End the controlled scope.
    void ctrl_end();
    
    /*! \brief Initialize an inverse scope.
     * 
     *  The quantum operation inside the scope is executed backwards.
     */
    void adj_begin();

    //! \brief End the inverse scope.
    void adj_end();
    
    //! \brief Measure a quant and return a future.
    future measure(const quant& q);
    
    /*! \brief Pauli-X gate (\f$\sigma_x\f$)
     *
     * Apply a single-qubit Pauli-X gate on every qubit of q.
     * 
     * **Matrix representation:**
     *
     * \f[
     *      X = \begin{bmatrix} 
     *              0 & 1 \\
     *              1 & 0 
     *          \end{bmatrix}
     * \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          X\left|0\right> = & \left|1\right> \\
     *          X\left|1\right> = & \left|0\right>
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     */
    quant X(const quant& q);

    /*! \brief Pauli-Y gate (\f$\sigma_y\f$)
     *
     * Apply a single-qubit Pauli-Y gate on every qubit of q.
     *
     * **Matrix representation:**
     * 
     *  \f[
     *      Y = \begin{bmatrix} 
     *              0 & -i \\
     *              i & 0 
     *          \end{bmatrix}
     *  \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          Y\left|0\right> = & -i\left|1\right> \\
     *          Y\left|1\right> =& i\left|1\right>
     *      \end{matrix}
     * 
     * \f]
     * 
     * \param q input qubits
     */   
    quant Y(const quant& q);
    
    /*! \brief Pauli-Z gate (\f$\sigma_z\f$)
     *
     * Apply a single-qubit Pauli-Y gate on every qubit of q.
     * 
     * **Matrix representation:**
     * 
     * \f[
     *      Z = \begin{bmatrix} 
     *              1 & 0 \\
     *              0 & -1 
     *          \end{bmatrix}
     * \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          Z\left|0\right> = & \left|0\right> \\ 
     *          Z\left|1\right> = & -\left|1\right>
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     */   
 
    quant Z(const quant& q);
    
    /*! \brief Hadamard gate
     * 
     * Apply a single-qubit Hadamard gate on every qubit of q.
     *
     * **Matrix representation:**
     * 
     * \f[
     *      H = \frac{1}{\sqrt{2}}\begin{bmatrix} 
     *              1 & 1 \\
     *              1 & -1 
     *          \end{bmatrix}
     * \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          H\left|0\right> = & \frac{\left|0\right>+\left|1\right>}{\sqrt{2}} = & \left|+\right> \\
     *          H\left|1\right> = & \frac{\left|0\right>-\left|1\right>}{\sqrt{2}} = & \left|-\right> \\
     *          H\left|+\right> = & \left|0\right> \\
     *          H\left|-\right> = & \left|1\right> \\
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     */   
    quant H(const quant& q);
    
    /*! \brief S gate (Phase)
     *
     * Apply a single-qubit S gate on every qubit of q.
     *
     * **Matrix representation:**
     * 
     * \f[
     *      S = \begin{bmatrix} 
     *              1 & 0 \\
     *              0 & i 
     *          \end{bmatrix}
     * \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          S\left|0\right> = & \left|0\right> \\
     *          S\left|1\right> = & i\left|1\right> 
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     */   
    quant S(const quant& q);
    
    /*! \brief S-dagger gate (S\f$^\dagger\f$)
     *
     * Apply a single-qubit S-dagger gate on every qubit of q.
     *
     * **Matrix representation:**
     * 
     * \f[
     *      S^\dagger = \begin{bmatrix} 
     *              1 & 0 \\
     *              0 & -i 
     *          \end{bmatrix}
     * \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          S^\dagger\left|0\right> = & \left|0\right> \\
     *          S^\dagger\left|1\right> = & -i\left|1\right> 
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     */   
    quant SD(const quant& q);
    
    /*! \brief T gate 
     *
     * Apply a single-qubit T gate on every qubit of q.
     *
     * **Matrix representation:**
     * 
     *  \f[
     *      T = \begin{bmatrix} 
     *              1 & 0 \\
     *              0 & e^{i\pi/4} 
     *          \end{bmatrix}
     *  \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          T\left|0\right> = & \left|0\right> \\
     *          T\left|1\right> = & \frac{1+i}{\sqrt{2}}\left|1\right> \end{matrix}
     * \f]
     * 
     * \param q input qubits
     */   
    quant T(const quant& q);
    
    /*! \brief T-dagger gate (T\f$^\dagger\f$)
     *
     * Apply a single-qubit T-dagger gate on every qubit of q.
     *
     * **Matrix representation:**
     * 
     *  \f[
     *      T^\dagger = \begin{bmatrix} 
     *                      1 & 0 \\
     *                      0 & e^{-i\pi/4}
     *                  \end{bmatrix}
     *  \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix} 
     *          T^\dagger\left|0\right> = & \left|0\right> \\
     *          T^\dagger\left|1\right> = & \frac{1-i}{\sqrt{2}}\left|1\right> \end{matrix}
     * \f]
     * 
     * \param q input qubits
     */   
    quant TD(const quant& q);
    
    /*! \brief Phase gate
     * 
     * Apply a relative phase of \f$\lambda\f$ on every qubit of q.
     *
     * **Matrix representation:**
     * 
     * \f[ 
     *      P(\lambda) = \begin{bmatrix}
     *                      1 & 0 \\
     *                      0 & e^{i\lambda}
     *                    \end{bmatrix}
     * \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix}
     *          P\left|0\right> = & \left|0\right> \\
     *          P\left|1\right> =& e^{i\lambda}\left|1\right> 
     *      \end{matrix}
     * 
     * \f]
     * 
     * \param q input qubits
     * \param lambda \f$\lambda\f$
     */   
    quant phase(double lambda, const quant& q);
         
    /*! \brief X-axis rotation gate
     * 
     * Apply a rotation of \f$\theta\f$ about the X-axis on every qubit of q.
     *
     * **Matrix representation:**
     * 
     *  \f[ 
     *       RX(\theta) = \begin{bmatrix} 
     *              \cos{\frac{\theta}{2}} & -i\sin{\frac{\theta}{2}} \\
     *            -i\sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}} 
     *                     \end{bmatrix}
     *  \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix}
     *          RX\left|0\right> = & \cos\frac{\theta}{2}\left|0\right> -i\sin\frac{\theta}{2}\left|1\right> \\
     *          RX\left|1\right> =& -i\sin\frac{\theta}{2}\left|0\right> + \cos\frac{\theta}{2}\left|1\right>
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     * \param theta \f$\theta\f$
     */   
    quant RX(double theta, const quant& q);

    /*! \brief Y-axis rotation gate
     * 
     * Apply a rotation of \f$\theta\f$ about the Y-axis on every qubit of q.
     *
     * **Matrix representation:**
     * 
     *  \f[ 
     *      RY(\theta) = \begin{bmatrix} 
     *          \cos{\frac{\theta}{2}} & -\sin{\frac{\theta}{2}} \\
     *          \sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}} 
     *                    \end{bmatrix}
     *  \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix}
     *          RY\left|0\right> = & \cos{\theta\over2}\left|0\right> -i\sin\frac{\theta}{2}\left|1\right> \\
     *          RY\left|1\right> =& -\sin\frac{\theta}{2}\left|0\right> + \cos\frac{\theta}{2}\left|1\right> 
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     * \param theta \f$\theta\f$
     */   
    quant RY(double theta, const quant& q);

    /*! \brief Z-axis rotation gate
     * 
     * Apply a rotation of \f$\theta\f$ about the Z-axis on every qubit of q.
     *
     * **Matrix representation:**
     * 
     *  \f[ 
     *      RZ(\theta) =  \begin{bmatrix} 
     *                      e^{-i\theta/2} & 0 \\
     *                                   0 & e^{i\theta/2} 
     *                    \end{bmatrix}
     *  \f]
     * 
     * **Effect:**
     * 
     * \f[
     *      \begin{matrix}
     *          RZ\left|0\right> = & e^{-i\theta/2}\left|0\right> \\ 
     *          RZ\left|1\right> =& e^{i\theta/2}\left|1\right> 
     *      \end{matrix}
     * \f]
     * 
     * \param q input qubits
     * \param theta \f$\theta\f$
     */   
    quant RZ(double theta, const quant& q);
    
    /*! \brief Dump the states of the qubits of q, used for debuggin.
     *  \class dump
     */
    class dump {
    public:

        //! \brief Dump the state of a quant.
        dump(const quant& q);

        //! \brief Return a list of basis states.
        std::vector<std::vector<unsigned long>> get_states();

        //! \brief Return a list of amplitudes of a state. 
        std::vector<std::complex<double>> amplitude(std::vector<unsigned long> idx);
        
        //! \brief Return the probability of measure a state.
        double probability(std::vector<unsigned long> idx);
        
        //! \brief Return a string to print the quantum state.
        std::string show(std::string format);

        //! \brief Triggers the quantum execution to get the dump.
        void get();

        //! \brief True when the states are equal.
        bool operator==(dump& other);

        //! \brief True when the states are different.
        bool operator!=(dump& other);

        const size_t nbits;
    private:
        const std::shared_ptr<bool> process_on_top;

        size_t id;
        std::shared_ptr<std::map<std::vector<std::uint64_t>, std::vector<std::complex<double>>>> states;
        std::shared_ptr<bool> available;
    };

    //! \brief Execute the top process.
    void exec_quantum();
    
    //! \brief Get process metrics.
    metrics report();

    /*! \brief Execute a Ket Bitwise plugin.
     *   
     *  The plugin must be acessable by the plugin path.
     *  All qubits are considered entangled after the plugin execution.  
     * 
     *  \param name lib<name>.so
     *  \param q qubits affected by the plugin
     *  \param args arguments for the plugin
     */
    void plugin(const std::string& name, const quant& q, const std::string& args);

    #ifndef SWIG
    //! \brief Run function in a new process.
    template <class F, class... Args> 
    auto run(F func, Args... args) {
        process_begin();
        auto ret = func(args...);
        process_end();
        return ret;
    }

    //! \brief Add qubits of control to a function.
    template <class F, class... Args>
    void ctrl(const quant& q, F gate, Args... args) {
        ctrl_begin(q);
        gate(args...);
        ctrl_end();
    }

    //! \brief Call the inverse of a function.
    template <class F, class... Args>
    void adj(F gate, Args... args) {
        adj_begin();
        gate(args...);
        adj_end();
    }
    
    //! \brief Call the inverse of a function adding qubits of controll.
    template <class F, class... Args>
    void ctrl_adj(const quant& q, F gate, Args... args) {
        ctrl_begin(q);
        adj_begin();
        gate(args...);
        adj_end();
        ctrl_end();
    }

    //! \brief If statement that runs on the quantum computer.
    template <class F>
    void qc_if(const future& test, F body) {
        label then{"if.then"};
        label end{"if.end"};

        branch(test, then, end);

        then.begin();

        body();

        jump(end);

        end.begin();
    } 
    #endif
}
