# ======================================================================================================================
#  fennec, a free and open source game engine
#  Copyright © 2025 - 2026  Medusa Slockbower
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
# ======================================================================================================================

import gdb
from collections import deque

# OPTIONAL =============================================================================================================
class OptionalPrinter:
    """Print a fennec::optional"""

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

    def to_string(self):
        is_initialized = self.val['_set']
        if is_initialized:
            return "{{ value = {} }}".format(self.val['_val'])
        else:
            return "{ empty }"

# PAIR =================================================================================================================
class PairPrinter:
    """Print a fennec::optional"""

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

    def to_string(self):
        return "{ first = " + str(self.val['first']) + ", second = " + str(self.val['second']) + " }"

    def children(self):
        return ("first", self.val['first']), ("second", self.val['second'])


# TUPLE ================================================================================================================

class TuplePrinter:
    """Print a fennec::tuple"""

    def __init__(self, val):
        self.fields = val.type.fields()[0].type.fields()
        self.elems  = val
        self.count  = len(self.fields)

    def to_string(self):
        return " { size = " + str(len(self.fields)) + " }"

    def children(self):
        return (('[{}]'.format(i), self.elems.cast(self.fields[i].type)['value']) for i in range(self.count))



# ARRAY ================================================================================================================
class ArrayPrinter:
    """Print a fennec::array"""

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

    def to_string(self):
        return "{ length = " + str(self.val['elements'].type.range()[1] + 1) + " }"

    def display_hint(self):
        return 'array'

    def children(self):
        start = self.val['elements']
        return (('[{}]'.format(i), start[i]) for i in range(0, self.val['elements'].type.range()[1] + 1))


# DYNARRAY =============================================================================================================
class DynArrayPrinter:
    """Print a fennec::dynarray"""

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

    def to_string(self):
        return "{ length " + str(self.val['_size']) + ", capacity " + str(self.val['_alloc']['_capacity']) + " }"

    def children(self):
        size = int(self.val['_size'])
        start = self.val['_alloc']['_data']
        return (('[{}]'.format(i), start[i]) for i in range(size))


# LIST =================================================================================================================
class ListPrinter:
    """Print a fennec::list"""

    class Iterator:
        def __init__(self, val):
            self.list = val
            self.node = self.list['_root']
            self.index = 0
            self.table = self.list['_table']['_data']

        def __iter__(self):
            return self

        def __next__(self):
            if self.node == 18446744073709551615:
                raise StopIteration

            i = self.index
            self.index = self.index + 1
            value = self.table[self.node]['value']['_val']
            self.node = self.table[self.node]['next']
            return '[{}]'.format(i), value



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

    def to_string(self):
        return "{ length " + str(self.val['_size']) + ", capacity " + str(self.val['_table']['_capacity']) + " }"

    def children(self):
        return self.Iterator(self.val)

    def display_hint(self):
        return 'array'


# DEQUE ================================================================================================================

class DequePrinter:
    """Print a fennec::deque"""

    class Iterator:
        def __init__(self, start):
            self.node = start
            self.index = 0

        def __iter__(self):
            return self

        def __next__(self):
            if self.node is None:
                raise StopIteration

            i = self.index
            value = self.node.dereference()['value']

            self.node = self.node.dereference()['next']
            self.index = self.index + 1

            return '[{}]'.format(i), value

    def __init__(self, val):
        self.first = val['_first']
        self.last  = val['_last']
        self.size  = val['_size']

    def to_string(self):
        if self.first is None:
            return "{ empty }"
        return "{ length " + str(self.size) + " }"

    def children(self):
        return self.Iterator(self.first)


# SET ==================================================================================================================

class SetPrinter:
    """Print a fennec::set"""

    class Iterator:
        def __init__(self, table, capacity):
            self.table = table
            self.capacity = capacity
            self.node = 0
            self.index = 0

        def __iter__(self):
            return self

        def __next__(self):
            while self.node < self.capacity:
                if self.table[self.node]['value']['_set']:
                    break
                self.node = self.node + 1

            if self.node >= self.capacity:
                raise StopIteration

            i = self.index
            value = self.table[self.node]['value']['_val']
            self.node = self.node + 1
            self.index = self.index + 1
            return '[{}]'.format(i), value

    def __init__(self, val):
        self.table    = val['_alloc']['_data']
        self.capacity = val['_alloc']['_capacity']
        self.size     = val['_size']

    def to_string(self):
        if self.size == 0:
            return "{ empty }"
        return "{ size = " + str(self.size) + " }"

    def children(self):
        return self.Iterator(self.table, self.capacity)


# MAP ==================================================================================================================

class MapPrinter:
    """Print a fennec::map"""

    class Iterator:
        def __init__(self, table, capacity):
            self.table = table
            self.capacity = capacity
            self.node = 0
            self.index = 0
            self.move = False

        def __iter__(self):
            return self

        def __next__(self):
            while self.node < self.capacity:
                if self.table[self.node]['value']['_set']:
                    break
                self.node = self.node + 1

            if self.node >= self.capacity:
                raise StopIteration

            i = self.index
            pair = self.table[self.node]['value']['_val']
            key = pair['first']
            val = pair['second']

            if not self.move:
                self.move = True
                return 'key' + str(i), key
            else:
                self.move = False
                self.index = self.index + 1
                self.node = self.node + 1
                return 'value' + str(i), val

    def __init__(self, val):
        self.table    = val['_set']['_alloc']['_data']
        self.capacity = val['_set']['_alloc']['_capacity']
        self.size     = val['_set']['_size']

    def to_string(self):
        if self.size == 0:
            return "{ empty }"
        return "{ size = " + str(self.size) + " }"

    def children(self):
        return self.Iterator(self.table, self.capacity)

    def display_hint(self):
        return 'map'


# OBJECT_POOL ==========================================================================================================
class ObjectPoolPrinter:
    """Print a fennec::object_pool"""

    class Iterator:
        def __init__(self, val):
            self.list = val
            self.index = 0
            self.capacity = self.list['_table']['_alloc']['_capacity']
            self.table = self.list['_table']['_alloc']['_data']

        def __iter__(self):
            return self

        def __next__(self):

            i = self.index
            while True:
                i = self.index
                self.index = self.index + 1

                if self.index >= self.capacity:
                    raise StopIteration

                if bool(self.table[i]['_set']):
                    value = self.table[i]['_val']
                    break

            return '[{}]'.format(i), value



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

    def to_string(self):
        return "{ length " + str(self.val['_size']) + ", capacity " + str(self.val['_table']['_alloc']['_capacity']) + " }"

    def children(self):
        return self.Iterator(self.val)

    def display_hint(self):
        return 'array'


# RDTREE ===============================================================================================================

class RDTreePrinter:
    """Print a fennec::rdtree"""

    class Iterator:
        def __init__(self, tree, node, capacity):
            self.tree = tree
            self.capacity = capacity
            self.visit = deque()

            self.visit.append((node, 0, 0))

        def __iter__(self):
            return self

        def __next__(self):
            if len(self.visit) == 0:
                raise StopIteration

            node  = self.visit[0][0]
            i     = self.visit[0][1]
            depth = self.visit[0][2]
            self.visit.popleft()

            value = self.tree[node]['value']

            nnext  = self.tree[node]['next']
            nprev  = self.tree[node]['prev']
            nprevc = self.tree[nprev]['child'] if nprev != 18446744073709551615 else 18446744073709551615
            child  = self.tree[node]['child']
            n_chld = self.tree[node]['num_children']

            index = '⠀' * depth * 2 # Uses Braille Space, otherwise it would get eaten as whitespace by parsers

            if nnext < self.capacity:
                self.visit.appendleft((nnext, i + 1, depth))

            if child < self.capacity:
                self.visit.appendleft((child, 0, depth + 1))

            # ┌ ─ ├ └

            if nnext != 18446744073709551615:
                index += '├'
            else:
                index += '└'

            index += '─'
            index += '[{}]'.format(node)
            return index, value


    def __init__(self, val):
        self.tree = val['_table']['_data']
        self.size = val['_size']
        self.capacity = val['_table']['_capacity']

    def to_string(self):
        if self.size == 0:
            return "{ empty }"
        return "{ size = " + str(self.size) + " }"

    def children(self):
        return self.Iterator(self.tree, 0, self.capacity)


# PRIORITY QUEUE =======================================================================================================

class PriorityQueuePrinter:
    """Print a fennec::rdtree"""

    class Iterator:
        def __init__(self, tree, node, capacity):
            self.tree = tree
            self.capacity = capacity
            self.visit = deque()
            self.skip = True

            self.visit.append((node, 0, 0, node))

        def __iter__(self):
            return self

        def __next__(self):
            if len(self.visit) == 0:
                raise StopIteration

            node  = self.visit[0][0]
            i     = self.visit[0][1]
            depth = self.visit[0][2]
            start = self.visit[0][3]
            self.visit.popleft()

            if node == start and not self.skip:
                return self.__next__()

            self.skip = False

            value = self.tree[node]['_val']['key']

            nnext  = self.tree[node]['_val']['next']
            child  = self.tree[node]['_val']['child']

            index = '⠀' * depth * 2 # Uses Braille Space, otherwise it would get eaten as whitespace by parsers

            if nnext < self.capacity:
                self.visit.appendleft((nnext, i + 1, depth, start))

            if child < self.capacity:
                self.visit.appendleft((child, 0, depth + 1, child))
                self.skip = True

            # ┌ ─ ├ └

            if nnext != 18446744073709551615:
                index += '├'
            else:
                index += '└'

            index += '─'
            index += '[{}]'.format(node)
            return index, value


    def __init__(self, val):
        self.tree     = val['_table']['_table']['_alloc']['_data']
        self.size     = val['_table']['_size']
        self.capacity = val['_table']['_table']['_alloc']['_capacity']
        self.min      = val['_min']

    def to_string(self):
        if self.size == 0:
            return "{ empty }"
        return "{ size = " + str(self.size) + " }"

    def children(self):
        return self.Iterator(self.tree, self.min, self.capacity)


# BINTREE ==============================================================================================================

class BinTreePrinter:
    """Print a fennec::bintree"""

    class Iterator:
        def __init__(self, tree, node, capacity):
            self.tree = tree
            self.capacity = capacity
            self.visit = deque()

            if capacity > 0:
                self.visit.append((node, 0, 0))

        def __iter__(self):
            return self

        def __next__(self):
            if len(self.visit) == 0:
                raise StopIteration

            node   = self.visit[0][0]
            i      = self.visit[0][1]
            depth  = self.visit[0][2]
            parent = self.tree[node]['parent']
            self.visit.popleft()

            value = self.tree[node]['value']
            left  = self.tree[node]['child'][0]
            right = self.tree[node]['child'][1]

            if right < self.capacity:
                self.visit.appendleft((right, 1, depth + 1))
            if left < self.capacity:
                self.visit.appendleft((left,  0, depth + 1))

            index = '⠀' * depth * 2 # Uses Braille Space, otherwise it would get eaten as whitespace by parsers
            if i == 0 and parent != 18446744073709551615 and self.tree[parent]['right'] != 18446744073709551615:
                index += '├'
            else:
                index += '└'

            index += '─'
            index += '[{}]'.format(node)
            return index, value


    def __init__(self, val):
        self.tree = val['_table']['_data']
        self.size = val['_size']
        self.root = val['_root']
        self.capacity = val['_table']['_capacity']

    def to_string(self):
        if self.size == 0:
            return "{ empty }"
        return "{ size = " + str(self.size) + " }"

    def children(self):
        return self.Iterator(self.tree, self.root, self.capacity)


# SEQUENCE =============================================================================================================

class SequencePrinter:
    """Print a fennec::sequence"""

    class Iterator:
        def __init__(self, node):
            self.visit = deque()

            if node is not None:
                self.visit.append((node, 0, 0))

        def __iter__(self):
            return self

        def __next__(self):
            if len(self.visit) == 0:
                raise StopIteration

            node   = self.visit[0][0]
            i      = self.visit[0][1]
            depth  = self.visit[0][2]
            self.visit.popleft()

            value = node['key']
            left  = node['child'][0]
            right = node['child'][1]
            print("it: ", node, " ", left, " ", right);

            if right != 0:
                self.visit.appendleft((right, 1, depth + 1))
            if left != 0:
                self.visit.appendleft((left,  0, depth + 1))

            index = '⠀' * depth * 2 # Uses Braille Space, otherwise it would get eaten as whitespace by parsers
            if i == 0:
                index += '├'
            else:
                index += '└'

            index += '─'
            index += '[{}]'.format(node)
            return index, value


    def __init__(self, val):
        self.size = val['_size']
        self.root = val['_root']

    def to_string(self):
        if self.size == 0:
            return "{ empty }"
        return "{ size = " + str(self.size) + " }"

    def children(self):
        print("root: ", self.root)
        return self.Iterator(self.root)


# Graph ================================================================================================================

class GraphPrinter:
    """Print a fennec::graph"""

    class Iterator:
        def __init__(self, val):
            self.node_pool = val['_vertex_pool']['_table']['_alloc']['_data']
            self.max_nodes = val['_vertex_pool']['_table']['_alloc']['_capacity']
            self.conn_map  = val['_edge_map']['_alloc']['_data']
            self.max_conn  = val['_edge_map']['_size']
            self.index     = 0

        def __iter__(self):
            return self

        def __next__(self):
            if self.index >= self.max_nodes:
                raise StopIteration

            i = self.index
            self.index = self.index + 1

            while not bool(self.node_pool[i]['_set']):
                i = self.index
                self.index = self.index + 1

            conns = self.get_conns(i)
            value = self.node_pool[i]['_val']

            return '[{} -> {{{}}}]'.format(i, str(conns)), value



        def get_conns(self, index):
            indices = []

            if index >= self.max_conn:
                return indices

            map = self.conn_map[index]['_set']
            max_conns = map['_alloc']['_capacity']
            conns = map['_alloc']['_data']

            print(max_conns)

            if max_conns == 0:
                return indices

            for i in range(0, max_conns):
                if bool(conns[i]['value']['_set']):
                    conn = conns[i]['value']['_val']
                    indices.append(str(int(conn['first'])))

            return indices

    def __init__(self, val):
        self.val = val
        self.size = val['_vertex_pool']['_size']

    def to_string(self):
        if self.size == 0:
            return "{ empty }"
        return "{ size = " + str(self.size) + " }"

    def children(self):
        return self.Iterator(self.val)



# GDB Code =============================================================================================================

def register_printers():
    print("registering containers")
    pp = gdb.printing.RegexpCollectionPrettyPrinter("fennec::containers")
    pp.add_printer('fennec::array',          '^fennec::array<.*>$',          ArrayPrinter)
    pp.add_printer('fennec::deque',          '^fennec::deque<.*>$',          DequePrinter)
    pp.add_printer('fennec::dynarray',       '^fennec::dynarray<.*>$',       DynArrayPrinter)
    pp.add_printer('fennec::graph',          '^fennec::graph<.*>$',          GraphPrinter)
    pp.add_printer('fennec::list',           '^fennec::list<.*>$',           ListPrinter)
    pp.add_printer('fennec::map',            '^fennec::map<.*>$',            MapPrinter)
    pp.add_printer('fennec::object_pool',    '^fennec::object_pool<.*>$',    ObjectPoolPrinter)
    pp.add_printer('fennec::optional',       '^fennec::optional<.*>$',       OptionalPrinter)
    pp.add_printer('fennec::pair',           '^fennec::pair<.*>$',           PairPrinter)
    pp.add_printer('fennec::set',            '^fennec::set<.*>$',            SetPrinter)
    pp.add_printer('fennec::rdtree',         '^fennec::rdtree<.*>$',         RDTreePrinter)
    pp.add_printer('fennec::bintree',        '^fennec::bintree<.*>$',        BinTreePrinter)
    pp.add_printer('fennec::sequence',       '^fennec::sequence<.*>$',       SequencePrinter)
    pp.add_printer('fennec::priority_queue', '^fennec::priority_queue<.*>$', PriorityQueuePrinter)
    pp.add_printer('fennec::tuple',          '^fennec::tuple<.*>$',          TuplePrinter)
    return pp

printer = register_printers()
