#!/usr/bin/env python3

# SPDX-FileCopyrightText: © 2024 Decompollaborate
# SPDX-License-Identifier: MIT

from __future__ import annotations

import rabbitizer

from ... import common

from .. import symbols

from . import SectionBase


class SectionGccExceptTable(SectionBase):
    """
    A `.gcc_except_table` section contains a single symbol that contains
    information to handle C++ exceptions known as the Error Handler Table
    (`ehtable` for short).

    The ehtable is a list of references to labels within functions or
    references to functions themselves. At the end of the table there are two
    `-1`, which seems to exist just as an end marker (TODO: corroborate the
    endmarker claim).

    Normally there's a single exception table per section per TU, but the way
    it was implemented here allows for multiple exception tables in case file
    splits have not been done yet.

    This is implemented similarly to how the jumptable handling is implemented.
    """

    def __init__(self, context: common.Context, vromStart: int, vromEnd: int, vram: int, filename: str, array_of_bytes: bytes, segmentVromStart: int, overlayCategory: str|None) -> None:
        super().__init__(context, vromStart, vromEnd, vram, filename, common.Utils.bytesToWords(array_of_bytes, vromStart, vromEnd), common.FileSectionType.GccExceptTable, segmentVromStart, overlayCategory)


    def _analyze_processExceptTable(self, localOffset: int, w: int, contextSym: common.ContextSymbol|None, lastVramSymbol: common.ContextSymbol, exceptTableSym: common.ContextSymbol|None, firstExceptTableWord: int) -> tuple[common.ContextSymbol|None, int]:
        if contextSym is not None and contextSym.isGccExceptTable():
            # New jumptable
            exceptTableSym = contextSym
            firstExceptTableWord = w

        elif exceptTableSym is not None:
            # The last symbol found was part of a jumptable, check if this word still is part of the jumptable

            if localOffset not in self.pointersOffsets:
                if w == 0xFFFFFFFF:
                    # Except tables contain 2 negative ones at the end of the table.
                    pass

                elif w == 0:
                    return None, firstExceptTableWord

                elif contextSym is not None:
                    return None, firstExceptTableWord

                elif ((w >> 24) & 0xFF) != ((firstExceptTableWord >> 24) & 0xFF):
                    if not (
                        lastVramSymbol.isGccExceptTable()
                        and lastVramSymbol.isGot
                        and common.GlobalConfig.GP_VALUE is not None
                    ):
                        return None, firstExceptTableWord
        else:
            # No jumptable
            return None, firstExceptTableWord

        # Generate the current label
        if w != 0xFFFFFFFF and w != 0:
            labelAddr = w
            if lastVramSymbol.isGot and common.GlobalConfig.GP_VALUE is not None:
                labelAddr = common.GlobalConfig.GP_VALUE + rabbitizer.Utils.from2Complement(w, 32)
            labelSym = self.addGccExceptTableLabel(labelAddr, isAutogenerated=True)

            if labelSym.unknownSegment:
                return None, firstExceptTableWord

            labelSym.referenceCounter += 1

        return exceptTableSym, firstExceptTableWord


    def analyze(self) -> None:
        lastVramSymbol: common.ContextSymbol = self._checkAndCreateFirstSymbol()
        contextSym: common.ContextSymbol|None = None

        # All symbols on this section are assumed to be except tables
        self.addGccExceptTable(self.getVramOffset(0), isAutogenerated=True, symbolVrom=self.getVromOffset(0))

        symbolList: list[tuple[int, int]] = []
        localOffset = 0

        exceptTableSym: common.ContextSymbol|None = None
        firstExceptTableWord = -1
        negativeOneCounter = 0

        for w in self.words:
            currentVram = self.getVramOffset(localOffset)
            currentVrom = self.getVromOffset(localOffset)

            if w == 0:
                if not lastVramSymbol.isAutoCreatedPad:
                    pad = self._addOwnedAutocreatedPad(localOffset)
                    if pad is not None:
                        lastVramSymbol = pad

            if negativeOneCounter >= 2 and w != 0:
                # The except table ended
                contextSym = self._addOwnedSymbol(localOffset)
                if contextSym is not None:
                    lastVramSymbol = contextSym
                    self.addGccExceptTable(currentVram, isAutogenerated=True, symbolVrom=currentVrom)

                negativeOneCounter = 0

            else:
                # Check if we have a symbol at this address, if not then use the last known one
                contextSym = self.getSymbol(currentVram, vromAddress=currentVrom, tryPlusOffset=False)
                if contextSym is not None:
                    lastVramSymbol = contextSym

            exceptTableSym, firstExceptTableWord = self._analyze_processExceptTable(localOffset, w, contextSym, lastVramSymbol, exceptTableSym, firstExceptTableWord)

            if exceptTableSym is None:
                if contextSym is not None or self.popPointerInDataReference(currentVram) is not None or (lastVramSymbol.isGccExceptTable() and w != 0):
                    # Somehow this isn't an except table? TODO: check if this can happen
                    contextSym = self._addOwnedSymbol(localOffset)
                    if contextSym is not None:
                        lastVramSymbol = contextSym

                self.checkWordIsASymbolReference(w)

            if contextSym is not None:
                self.symbolsVRams.add(currentVram)
                symbolList.append((localOffset, currentVram))

                self._createAutoPadFromSymbol(localOffset, contextSym)

            if w == 0xFFFFFFFF:
                negativeOneCounter += 1

            localOffset += 4

        for i, (offset, vram) in enumerate(symbolList):
            if i + 1 == len(symbolList):
                words = self.words[offset//4:]
            else:
                nextOffset = symbolList[i+1][0]
                words = self.words[offset//4:nextOffset//4]

            vrom = self.getVromOffset(offset)
            vromEnd = vrom + len(words)*4
            sym = symbols.SymbolGccExceptTable(self.context, vrom, vromEnd, offset + self.inFileOffset, vram, words, self.segmentVromStart, self.overlayCategory)
            sym.parent = self
            sym.setCommentOffset(self.commentOffset)
            sym.analyze()
            self.symbolList.append(sym)

        self.processStaticRelocs()
