"""
********************************************************************************
* Copyright (c) 2025 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""

from qrisp.jasp.primitives import get_qubit, slice_qb_array, get_size, fuse_qb_array, AbstractQubit


class DynamicQubitArray:

    def __init__(self, tracer):
        self.tracer = tracer

    def __getitem__(self, key):
        tracer = self.tracer
        if isinstance(key, slice):
            start = key.start
            if key.start is None:
                start = 0
            stop = key.stop
            if key.stop is None:
                stop = get_size(tracer)

            return DynamicQubitArray(slice_qb_array(tracer, start, stop))
        else:
            from qrisp.jasp.tracing_logic.tracing_quantum_session import (
                TracingQuantumSession,
            )

            qs = TracingQuantumSession.get_instance()
            id_tuple = (id(tracer), id(key))
            if not id_tuple in qs.qubit_cache:
                qs.qubit_cache[id_tuple] = get_qubit(tracer, key)
            return qs.qubit_cache[id_tuple]

    @property
    def size(self):
        return get_size(self.tracer)

    def __add__(self, other):
        if isinstance(other, DynamicQubitArray):
            other = other.tracer
        if isinstance(other, list):
            temp = self
            for x in other:
                if not isinstance(other, AbstractQubit):
                    raise Exception("Can only concatenate type AbstractQubit or list[AbstractQubit] to DynamicQubitArray")
                temp = temp + x
            return temp
        return DynamicQubitArray(fuse_qb_array(self.tracer, other))
    
    def __radd__(self, other):
        if isinstance(other, DynamicQubitArray):
            other = other.tracer
        if isinstance(other, list):
            temp = self
            for x in other[::-1]:
                if not isinstance(other, AbstractQubit):
                    raise Exception("Can only concatenate type AbstractQubit or list[AbstractQubit] to DynamicQubitArray")
                temp = x + temp
            return temp
        
        return DynamicQubitArray(fuse_qb_array(other, self.tracer))
        

    @property
    def reg(self):
        return self

    def measure(self):
        from qrisp.jasp import TracingQuantumSession, Measurement_p

        qs = TracingQuantumSession.get_instance()
        res, qs.abs_qc = Measurement_p.bind(self.tracer, qs.abs_qc)
        return res


from jax import tree_util
from builtins import id


def flatten_dqa(dqa):
    return (dqa.tracer,), None


def unflatten_dqa(aux_data, children):
    return DynamicQubitArray(children[0])


tree_util.register_pytree_node(DynamicQubitArray, flatten_dqa, unflatten_dqa)
