#!/usr/bin/env python3

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

from __future__ import annotations

import ast
import argparse
import bisect
import dataclasses
import enum
import os

from . import Utils
from .GlobalConfig import GlobalConfig
from .FileSectionType import FileSectionType


class ContextSegment:
    def __init__(self, segmentName: str, segmentInputPath: str, segmentType: str, subsections):
        self.name: str = segmentName
        self.inputPath: str = segmentInputPath
        self.type: str = segmentType
        self.subsections: list = subsections

class SymbolSpecialType(enum.Enum):
    function        = enum.auto()
    branchlabel     = enum.auto()
    jumptable       = enum.auto()
    jumptablelabel  = enum.auto()
    fakefunction    = enum.auto()
    hardwarereg     = enum.auto()
    constant        = enum.auto()


    def toStr(self) -> str:
        return "@" + self.name

    @staticmethod
    def fromStr(symTypeStr: str|None) -> SymbolSpecialType|None:
        if symTypeStr == "@function":
            return SymbolSpecialType.function
        if symTypeStr == "@branchlabel":
            return SymbolSpecialType.branchlabel
        if symTypeStr == "@jumptable":
            return SymbolSpecialType.jumptable
        if symTypeStr == "@jumptablelabel":
            return SymbolSpecialType.jumptablelabel
        if symTypeStr == "@fakefunction":
            return SymbolSpecialType.fakefunction
        if symTypeStr == "@hardwarereg":
            return SymbolSpecialType.hardwarereg
        if symTypeStr == "@constant":
            return SymbolSpecialType.constant
        return None


@dataclasses.dataclass
class ContextSymbolBase:
    name: str
    size: int = 4
    type: SymbolSpecialType|str|None = None

    sectionType: FileSectionType = FileSectionType.Unknown

    isDefined: bool = False
    "This symbol exists in any of the analyzed sections"
    isUserDeclared: bool = False
    "Declared externally by the user, but it may have not been found yet"
    isAutogenerated: bool = False
    "This symbol was automatically generated by the disassembler"

    isMaybeString: bool = False

    referenceCounter: int = 0
    "How much this symbol is referenced by something else"

    address: int = 0
    "generic variable as a placeholder for vram or offset"


    def hasNoType(self) -> bool:
        return self.type is None or self.type == ""


    def isTrustableFunction(self, rsp: bool=False) -> bool:
        """Checks if the function symbol should be trusted based on the current disassembler settings.

        Note: This function doesn't check if the type of the symbol is SymbolSpecialType.function
        """
        if GlobalConfig.TRUST_USER_FUNCTIONS and self.isUserDeclared:
            return True

        if GlobalConfig.TRUST_JAL_FUNCTIONS and self.isAutogenerated:
            return True

        if rsp:
            return True

        return False


    def isByte(self) -> bool:
        if not GlobalConfig.USE_DOT_BYTE:
            return False
        return self.type in ("u8", "s8")

    def isShort(self) -> bool:
        if not GlobalConfig.USE_DOT_SHORT:
            return False
        return self.type in ("u16", "s16")


    def isString(self) -> bool:
        if self.type == "char" or self.type == "char*":
            return True
        elif self.hasNoType(): # no type information, let's try to guess
            if GlobalConfig.STRING_GUESSER and self.isMaybeString:
                return True
        return False

    def isFloat(self) -> bool:
        return self.type in ("f32", "Vec3f")

    def isDouble(self) -> bool:
        return self.type == "f64"

    def isJumpTable(self) -> bool:
        return self.type == SymbolSpecialType.jumptable

    def isMaybeConstVariable(self) -> bool:
        if self.isFloat():
            return False
        if self.isDouble():
            return False
        elif self.isJumpTable():
            return False
        elif self.isString():
            return False
        return True


    def isStatic(self) -> bool:
        return self.name.startswith(".")

    def isLateRodata(self) -> bool:
        # if self.referenceCounter > 1: return False # ?
        return self.isJumpTable() or self.isFloat() or self.isDouble()


    def setTypeIfUnset(self, varType: str) -> bool:
        if self.hasNoType():
            self.type = varType
            return True
        return False


    def getName(self) -> str:
        return self.name

    def getType(self) -> str:
        if self.type is None:
            return ""
        if isinstance(self.type, SymbolSpecialType):
            return self.type.toStr()
        return self.type

    def getSymbolPlusOffset(self, address: int) -> str:
        if self.address == address:
            return self.name
        if self.address > address:
            return f"{self.name} - 0x{self.address - address:X}"
        return f"{self.name} + 0x{address - self.address:X}"

    def getSymbolLabel(self) -> str:
        label = ""
        if self.isStatic():
            label += "/* static variable */" + GlobalConfig.LINE_ENDS
        if self.sectionType == FileSectionType.Text:
            label += GlobalConfig.ASM_TEXT_LABEL
        else:
            label += GlobalConfig.ASM_DATA_LABEL
        label += " " + self.getName()
        return label


    def toCsv(self) -> str:
        return f"0x{self.address:06X},{self.name},{self.getType()},0x{self.size:X},{self.sectionType.toStr()},{self.isDefined},{self.isUserDeclared},{self.isAutogenerated},{self.isMaybeString},{self.referenceCounter}"


class ContextSymbol(ContextSymbolBase):
    def __init__(self, vram: int, name: str, *args, **kwargs):
        super().__init__(name, *args, **kwargs)
        self.address = vram

    @property
    def vram(self) -> int:
        return self.address

class ContextOffsetSymbol(ContextSymbolBase):
    def __init__(self, offset: int, name: str, sectionType: FileSectionType, *args, **kwargs):
        super().__init__(name, *args, sectionType=sectionType, **kwargs)
        self.address = offset

    # Relative to the start of the section
    @property
    def offset(self) -> int:
        return self.address

    def getName(self) -> str:
        if self.isStatic():
            return self.name[1:]
        return self.name


class ContextRelocSymbol(ContextSymbolBase):
    relocSection: FileSectionType
    relocType: int = -1 # Same number as the .elf specification

    def __init__(self, offset: int, name: str, relocSection: FileSectionType, *args, **kwargs):
        super().__init__(name, *args, **kwargs)
        self.address = offset
        self.relocSection = relocSection

    # Relative to the start of the section
    @property
    def offset(self) -> int:
        return self.address

    def getNamePlusOffset(self, offset: int) -> str:
        if offset == 0:
            return self.name
        if offset < 0:
            return f"{self.name} - 0x{-offset:X}"
        return f"{self.name} + 0x{offset:X}"

    def toCsv(self) -> str:
        return super().toCsv() + f",{self.relocSection.toStr()},{self.relocType}"


class Context:
    N64DefaultBanned = {0x80000010, 0x80000020}

    N64LibultraSyms = {
        0x80000300: ("osTvType",       "u32", 0x4),
        0x80000304: ("osRomType",      "u32", 0x4),
        0x80000308: ("osRomBase",      "u32", 0x4),
        0x8000030C: ("osResetType",    "u32", 0x4),
        0x80000310: ("osCicId",        "u32", 0x4),
        0x80000314: ("osVersion",      "u32", 0x4),
        0x80000304: ("osRomType",      "u32", 0x4),
        0x80000318: ("osMemSize",      "u32", 0x4),
        0x8000031C: ("osAppNmiBuffer", "u8",  0x40),
    }

    N64HardwareRegs = {
        # Signal Processor Registers
        0xA4040000: "SP_MEM_ADDR_REG",
        0xA4040004: "SP_DRAM_ADDR_REG",
        0xA4040008: "SP_RD_LEN_REG",
        0xA404000C: "SP_WR_LEN_REG",
        0xA4040010: "SP_STATUS_REG",
        0xA4040014: "SP_DMA_FULL_REG",
        0xA4040018: "SP_DMA_BUSY_REG",
        0xA404001C: "SP_SEMAPHORE_REG",

        0xA4080000: "SP_PC",

        # Display Processor Command Registers / Rasterizer Interface
        0xA4100000: "DPC_START_REG",
        0xA4100004: "DPC_END_REG",
        0xA4100008: "DPC_CURRENT_REG",
        0xA410000C: "DPC_STATUS_REG",
        0xA4100010: "DPC_CLOCK_REG",
        0xA4100014: "DPC_BUFBUSY_REG",
        0xA4100018: "DPC_PIPEBUSY_REG",
        0xA410001C: "DPC_TMEM_REG",

        # Display Processor Span Registers
        0xA4200000: "DPS_TBIST_REG", # DPS_TBIST_REG / DP_TMEM_BIST
        0xA4200004: "DPS_TEST_MODE_REG",
        0xA4200008: "DPS_BUFTEST_ADDR_REG",
        0xA420000C: "DPS_BUFTEST_DATA_REG",

        # MIPS Interface Registers
        0xA4300000: "MI_MODE_REG", # MI_MODE_REG / MI_INIT_MODE_REG
        0xA4300004: "MI_VERSION_REG",
        0xA4300008: "MI_INTR_REG",
        0xA430000C: "MI_INTR_MASK_REG",

        # Video Interface Registers
        0xA4400000: "VI_STATUS_REG", # VI_STATUS_REG / VI_CONTROL_REG
        0xA4400004: "VI_DRAM_ADDR_REG", # VI_DRAM_ADDR_REG / VI_ORIGIN_REG
        0xA4400008: "VI_WIDTH_REG",
        0xA440000C: "VI_INTR_REG",
        0xA4400010: "VI_CURRENT_REG",
        0xA4400014: "VI_BURST_REG", # VI_BURST_REG / VI_TIMING_REG
        0xA4400018: "VI_V_SYNC_REG",
        0xA440001C: "VI_H_SYNC_REG",
        0xA4400020: "VI_LEAP_REG",
        0xA4400024: "VI_H_START_REG",
        0xA4400028: "VI_V_START_REG",
        0xA440002C: "VI_V_BURST_REG",
        0xA4400030: "VI_X_SCALE_REG",
        0xA4400034: "VI_Y_SCALE_REG",

        # Audio Interface Registers
        0xA4500000: "AI_DRAM_ADDR_REG",
        0xA4500004: "AI_LEN_REG",
        0xA4500008: "AI_CONTROL_REG",
        0xA450000C: "AI_STATUS_REG",
        0xA4500010: "AI_DACRATE_REG",
        0xA4500014: "AI_BITRATE_REG",

        # Peripheral/Parallel Interface Registers
        0xA4600000: "PI_DRAM_ADDR_REG",
        0xA4600004: "PI_CART_ADDR_REG",
        0xA4600005: "D_A4600005", # TODO: figure out its name
        0xA4600006: "D_A4600006", # TODO: figure out its name
        0xA4600007: "D_A4600007", # TODO: figure out its name
        0xA4600008: "PI_RD_LEN_REG",
        0xA460000C: "PI_WR_LEN_REG",
        0xA4600010: "PI_STATUS_REG",
        0xA4600014: "PI_BSD_DOM1_LAT_REG", # PI dom1 latency
        0xA4600018: "PI_BSD_DOM1_PWD_REG", # PI dom1 pulse width
        0xA460001C: "PI_BSD_DOM1_PGS_REG", # PI dom1 page size
        0xA4600020: "PI_BSD_DOM1_RLS_REG", # PI dom1 release
        0xA4600024: "PI_BSD_DOM2_LAT_REG", # PI dom2 latency
        0xA4600028: "PI_BSD_DOM2_LWD_REG", # PI dom2 pulse width
        0xA460002C: "PI_BSD_DOM2_PGS_REG", # PI dom2 page size
        0xA4600030: "PI_BSD_DOM2_RLS_REG", # PI dom2 release

        # RDRAM Interface Registers
        0xA4700000: "RI_MODE_REG",
        0xA4700004: "RI_CONFIG_REG",
        0xA4700008: "RI_CURRENT_LOAD_REG",
        0xA470000C: "RI_SELECT_REG",
        0xA4700010: "RI_REFRESH_REG",
        0xA4700014: "RI_LATENCY_REG",
        0xA4700018: "RI_RERROR_REG",
        0xA470001C: "RI_WERROR_REG",

        # Serial Interface Registers
        0xA4800000: "SI_DRAM_ADDR_REG",
        0xA4800004: "SI_PIF_ADDR_RD64B_REG",
        0xA4800008: "D_A4800008", # reserved
        0xA480000C: "D_A480000C", # reserved
        0xA4800010: "SI_PIF_ADDR_WR64B_REG",
        0xA4800014: "D_A4800014", # reserved
        0xA4800018: "SI_STATUS_REG",
    }
    "N64 OS hardware registers"

    def __init__(self):
        self.segments: dict[str, ContextSegment] = dict()

        self.funcsInFiles: dict[str, list[int]] = dict()

        self.functions: dict[int, ContextSymbol] = dict()

        self.labels: dict[int, ContextSymbol] = dict()

        self.symbols: dict[int, ContextSymbol] = dict()
        self.symbolsVramSorted: list[int] = list()

        # Functions jumped into Using J instead of JAL
        self.fakeFunctions: dict[int, ContextSymbol] = dict()

        self.constants: dict[int, ContextSymbol] = dict()

        self.newPointersInData: set[int] = set()

        self.loPatches: dict[int, int] = dict()
        "key: address of %lo, value: symbol's vram to use instead"

        self.dataSymbolsWithReferencesWithAddends: set[int] = set()
        "Contains the address of data symbols which are allowed to have references to other symbols with addends"

        self.dataReferencingConstants: set[int] = set()
        "Set of addresses of data symbols which are allowed to reference named constants"

        # Stuff that looks like pointers, but the disassembler shouldn't count it as a pointer
        self.bannedSymbols: set[int] = set()

        # First key is the section type, sub key is offset relative to the start of that section
        self.offsetSymbols: dict[FileSectionType, dict[int, ContextOffsetSymbol]] = {
            FileSectionType.Text: dict(),
            FileSectionType.Data: dict(),
            FileSectionType.Rodata: dict(),
            FileSectionType.Bss: dict(),
        }

        self.relocSymbols: dict[FileSectionType, dict[int, ContextRelocSymbol]] = {
            FileSectionType.Text: dict(),
            FileSectionType.Data: dict(),
            FileSectionType.Rodata: dict(),
            FileSectionType.Bss: dict(),
        }

        # Where the jump table is
        self.offsetJumpTables: dict[int, ContextOffsetSymbol] = dict()
        # The addresses every jump table has
        self.offsetJumpTablesLabels: dict[int, ContextOffsetSymbol] = dict()


    def getAnySymbol(self, vramAddress: int) -> ContextSymbol|None:
        "Searches for any known function, label and symbol"
        if vramAddress == 0:
            return None

        if vramAddress in self.functions:
            return self.functions[vramAddress]

        if vramAddress in self.labels:
            return self.labels[vramAddress]

        if vramAddress in self.symbols:
            return self.symbols[vramAddress]

        if vramAddress in self.fakeFunctions:
            return self.fakeFunctions[vramAddress]

        return None

    def getGenericLabel(self, vramAddress: int) -> ContextSymbol|None:
        "Searches for any .text label (function, branch label or jtbl label)"
        if vramAddress == 0:
            return None

        if vramAddress in self.functions:
            return self.functions[vramAddress]

        if vramAddress in self.labels:
            return self.labels[vramAddress]

        if vramAddress in self.fakeFunctions:
            return self.fakeFunctions[vramAddress]

        return None

    def getGenericSymbol(self, vramAddress: int, tryPlusOffset: bool = True, checkUpperLimit: bool = True) -> ContextSymbol|None:
        "Searches a function or a symbol, or optionally for a symbol with an addend if `tryPlusOffset` is True"
        if vramAddress == 0:
            return None

        if vramAddress in self.functions:
            return self.functions[vramAddress]

        if vramAddress in self.symbols:
            return self.symbols[vramAddress]

        if vramAddress in self.fakeFunctions:
            return self.fakeFunctions[vramAddress]

        if GlobalConfig.PRODUCE_SYMBOLS_PLUS_OFFSET and tryPlusOffset:
            vramIndex = bisect.bisect(self.symbolsVramSorted, vramAddress)
            if vramIndex != len(self.symbolsVramSorted):
                symVram = self.symbolsVramSorted[vramIndex-1]
                contextSym = self.symbols[symVram]

                if vramAddress > symVram:
                    if checkUpperLimit and vramAddress >= symVram + contextSym.size:
                        return None
                    return contextSym
        return None

    def getSymbol(self, vramAddress: int, tryPlusOffset: bool = True, checkUpperLimit: bool = True) -> ContextSymbol|None:
        "Searches symbol or a symbol with an addend if `tryPlusOffset` is True"
        if vramAddress == 0:
            return None

        if vramAddress in self.symbols:
            return self.symbols[vramAddress]

        if GlobalConfig.PRODUCE_SYMBOLS_PLUS_OFFSET and tryPlusOffset:
            vramIndex = bisect.bisect(self.symbolsVramSorted, vramAddress)
            if vramIndex != len(self.symbolsVramSorted):
                symVram = self.symbolsVramSorted[vramIndex-1]
                contextSym = self.symbols[symVram]

                symbolSize = contextSym.size
                if vramAddress > symVram:
                    if checkUpperLimit:
                        if vramAddress >= symVram + symbolSize:
                            return None
                    return contextSym
        return None

    def getFunction(self, vramAddress: int) -> ContextSymbol|None:
        if vramAddress == 0:
            return None

        if vramAddress in self.functions:
            return self.functions[vramAddress]

        return None

    def getConstant(self, constantValue: int) -> ContextSymbol|None:
        if constantValue in self.constants:
            return self.constants[constantValue]

        return None

    def getLoPatch(self, loInstrVram: int|None) -> int|None:
        if loInstrVram is None:
            return None
        return self.loPatches.get(loInstrVram, None)


    def getOffsetSymbol(self, offset: int, sectionType: FileSectionType) -> ContextOffsetSymbol|None:
        if sectionType in self.offsetSymbols:
            symbolsInSection = self.offsetSymbols[sectionType]
            if offset in symbolsInSection:
                return symbolsInSection[offset]
        return None

    def getOffsetGenericSymbol(self, offset: int, sectionType: FileSectionType) -> ContextOffsetSymbol|None:
        if offset in self.offsetJumpTables:
            return self.offsetJumpTables[offset]

        if sectionType in self.offsetSymbols:
            symbolsInSection = self.offsetSymbols[sectionType]
            if offset in symbolsInSection:
                return symbolsInSection[offset]

        return None

    def getRelocSymbol(self, offset: int, sectionType: FileSectionType) -> ContextRelocSymbol|None:
        if sectionType in self.relocSymbols:
            relocsInSection = self.relocSymbols[sectionType]
            if offset in relocsInSection:
                return relocsInSection[offset]
        return None

    def getOffsetGenericLabel(self, offset: int, sectionType: FileSectionType) -> ContextOffsetSymbol|None:
        if offset in self.offsetJumpTablesLabels:
            return self.offsetJumpTablesLabels[offset]
        return None


    def addSymbol(self, vramAddress: int, name: str|None, sectionType: FileSectionType=FileSectionType.Unknown, isAutogenerated: bool=False) -> ContextSymbol:
        if name is None:
            name = f"D_{vramAddress:06X}"
            if GlobalConfig.AUTOGENERATED_NAMES_BASED_ON_SECTION_TYPE:
                if sectionType == FileSectionType.Rodata:
                    name = f"R_{vramAddress:06X}"
                elif sectionType == FileSectionType.Bss:
                    name = f"B_{vramAddress:06X}"
        if vramAddress not in self.symbols:
            contextSymbol = ContextSymbol(vramAddress, name)
            contextSymbol.isAutogenerated = isAutogenerated
            contextSymbol.sectionType = sectionType
            self.symbols[vramAddress] = contextSymbol
            bisect.insort(self.symbolsVramSorted, vramAddress)
        else:
            contextSymbol = self.symbols[vramAddress]
        if contextSymbol.isAutogenerated:
            contextSymbol.name = name
        return contextSymbol

    def addFunction(self, vramAddress: int, name: str|None=None, isAutogenerated: bool=False) -> ContextSymbol:
        if vramAddress not in self.functions:
            if name is None:
                name = f"func_{vramAddress:08X}"
            contextSymbol = ContextSymbol(vramAddress, name)
            contextSymbol.type = SymbolSpecialType.function
            contextSymbol.isAutogenerated = isAutogenerated
            self.functions[vramAddress] = contextSymbol
        else:
            contextSymbol = self.functions[vramAddress]
        contextSymbol.sectionType = FileSectionType.Text

        if vramAddress in self.fakeFunctions:
            del self.fakeFunctions[vramAddress]

        return contextSymbol

    def addBranchLabel(self, vramAddress: int, name: str|None=None, isAutogenerated: bool=False) -> ContextSymbol:
        if name is None:
            name = f".L{vramAddress:08X}"
        if vramAddress not in self.labels:
            contextSymbol = ContextSymbol(vramAddress, name)
            contextSymbol.isAutogenerated = isAutogenerated
            self.labels[vramAddress] = contextSymbol
        else:
            contextSymbol = self.labels[vramAddress]
        contextSymbol.type = SymbolSpecialType.branchlabel
        if contextSymbol.isAutogenerated:
            contextSymbol.name = name
        return contextSymbol

    def addJumpTable(self, vramAddress: int, name: str|None=None, isAutogenerated: bool=False) -> ContextSymbol:
        if name is None:
            name = f"jtbl_{vramAddress:08X}"
        contextSymbol = self.addSymbol(vramAddress, name, isAutogenerated=isAutogenerated)
        contextSymbol.type = SymbolSpecialType.jumptable
        if contextSymbol.isAutogenerated:
            contextSymbol.name = name
        return contextSymbol

    def addJumpTableLabel(self, vramAddress: int, name: str|None=None, isAutogenerated: bool=False) -> ContextSymbol:
        if name is None:
            name = f"L{vramAddress:08X}"
        if vramAddress not in self.labels:
            contextSymbol = ContextSymbol(vramAddress, name)
            contextSymbol.isAutogenerated = isAutogenerated
            self.labels[vramAddress] = contextSymbol
        else:
            contextSymbol = self.labels[vramAddress]
        contextSymbol.type = SymbolSpecialType.jumptablelabel
        if contextSymbol.isAutogenerated:
            contextSymbol.name = name
        return contextSymbol

    def addFakeFunction(self, vramAddress: int, name: str, isAutogenerated: bool=False) -> ContextSymbol:
        if vramAddress not in self.fakeFunctions:
            contextSymbol = ContextSymbol(vramAddress, name)
            contextSymbol.type = SymbolSpecialType.fakefunction
            contextSymbol.isAutogenerated = isAutogenerated
            self.fakeFunctions[vramAddress] = contextSymbol
            return contextSymbol
        return self.fakeFunctions[vramAddress]

    def addConstant(self, constantValue: int, name: str) -> ContextSymbol:
        if constantValue not in self.constants:
            contextSymbol = ContextSymbol(constantValue, name)
            contextSymbol.type = SymbolSpecialType.constant
            self.constants[constantValue] = contextSymbol
            return contextSymbol
        return self.constants[constantValue]

    def addOffsetJumpTable(self, offset: int, sectionType: FileSectionType) -> ContextOffsetSymbol:
        if offset not in self.offsetJumpTables:
            contextOffsetSym = ContextOffsetSymbol(offset, f"jtbl_{offset:06X}", sectionType)
            contextOffsetSym.type = SymbolSpecialType.jumptable
            self.offsetJumpTables[offset] = contextOffsetSym
            return contextOffsetSym
        return self.offsetJumpTables[offset]

    def addOffsetJumpTableLabel(self, offset: int, name: str, sectionType: FileSectionType) -> ContextOffsetSymbol:
        if offset not in self.offsetJumpTablesLabels:
            contextOffsetSym = ContextOffsetSymbol(offset, name, sectionType)
            contextOffsetSym.type = SymbolSpecialType.jumptablelabel
            self.offsetJumpTablesLabels[offset] = contextOffsetSym
            return contextOffsetSym
        return self.offsetJumpTablesLabels[offset]


    def fillDefaultBannedSymbols(self):
        self.bannedSymbols |= self.N64DefaultBanned

    def fillLibultraSymbols(self):
        for vram, (name, type, size) in self.N64LibultraSyms.items():
            contextSym = self.addSymbol(vram, name)
            contextSym.type = type
            contextSym.size = size
            contextSym.isDefined = True
            contextSym.isUserDeclared = True

    def fillHardwareRegs(self, useRealNames: bool=False):
        for vram, name in self.N64HardwareRegs.items():
            nameToUse = None
            if useRealNames:
                nameToUse = name
            contextSym = self.addSymbol(vram, nameToUse)
            contextSym.type = SymbolSpecialType.hardwarereg
            contextSym.size = 4
            contextSym.isDefined = True
            contextSym.isUserDeclared = True


    def readMMAddressMaps(self, filesPath: str, functionsPath: str, variablesPath: str):
        with open(filesPath) as infile:
            files_spec = ast.literal_eval(infile.read())

        for segmentName, segmentInputPath, segmentType, subsections, subfiles  in files_spec:
            self.segments[segmentName] = ContextSegment(segmentName, segmentInputPath, segmentType, subsections)
            for vram, subname in subfiles.items():
                if subname == "":
                    subname = f"{segmentName}_{vram:08X}"

        with open(functionsPath) as infile:
            functions_ast = ast.literal_eval(infile.read())

        for vram, funcData in functions_ast.items():
            funcName = funcData[0]
            self.addFunction(vram, funcName)
            self.functions[vram].isUserDeclared = True

        with open(variablesPath) as infile:
            variables_ast = ast.literal_eval(infile.read())

        for vram, varData in variables_ast.items():
            varName, varType, varArrayInfo, varSize = varData
            if varType == "":
                varType = None

            contextSym = self.addSymbol(vram, varName)
            contextSym.type = varType
            contextSym.size = varSize
            contextSym.isUserDeclared = True

    def readVariablesCsv(self, filepath: str):
        if not os.path.exists(filepath):
            return

        variables_file = Utils.readCsv(filepath)
        for row in variables_file:
            if len(row) == 0:
                continue

            varType: SymbolSpecialType|str|None
            vramStr, varName, varType, varSizeStr = row

            if vramStr == "-":
                continue

            vram = int(vramStr, 16)
            varSize = int(varSizeStr, 16)
            if varType == "":
                varType = None

            specialType = SymbolSpecialType.fromStr(varType)
            if specialType is not None:
                varType = specialType
                if specialType == SymbolSpecialType.function:
                    contextSym = self.addFunction(vram, varName)
                elif specialType == SymbolSpecialType.branchlabel:
                    contextSym = self.addBranchLabel(vram, varName)
                elif specialType == SymbolSpecialType.jumptable:
                    contextSym = self.addJumpTable(vram, varName)
                elif specialType == SymbolSpecialType.jumptablelabel:
                    contextSym = self.addJumpTableLabel(vram, varName)
                elif specialType == SymbolSpecialType.fakefunction:
                    contextSym = self.addFakeFunction(vram, varName)
                elif specialType == SymbolSpecialType.hardwarereg:
                    contextSym = self.addSymbol(vram, varName)
                else:
                    contextSym = self.addSymbol(vram, varName)
            else:
                contextSym = self.addSymbol(vram, varName)

            contextSym.type = varType
            contextSym.size = varSize
            contextSym.isUserDeclared = True

    def readFunctionsCsv(self, filepath: str):
        if not os.path.exists(filepath):
            return

        functions_file = Utils.readCsv(filepath)
        for row in functions_file:
            if len(row) == 0:
                continue

            vramStr, funcName = row

            if vramStr == "-":
                continue

            vram = int(vramStr, 16)
            contextSym = self.addFunction(vram, funcName)
            contextSym.isUserDeclared = True

    def readConstantsCsv(self, filepath: str):
        if not os.path.exists(filepath):
            return

        constants_file = Utils.readCsv(filepath)
        for row in constants_file:
            if len(row) == 0:
                continue

            constantValueStr, constantName = row

            if constantValueStr == "-":
                continue

            constantValue = int(constantValueStr, 16)
            contextSym = self.addConstant(constantValue, constantName)
            contextSym.isUserDeclared = True


    def saveContextToFile(self, filepath: str):
        with open(filepath, "w") as f:
            for address, symbol in self.functions.items():
                f.write(f"function,{symbol.toCsv()}\n")

            for address, symbol in self.labels.items():
                f.write(f"label,{symbol.toCsv()}\n")

            for address, symbol in self.symbols.items():
                f.write(f"symbol,{symbol.toCsv()}\n")

            for address, symbol in self.fakeFunctions.items():
                f.write(f"fake_function,{symbol.toCsv()}\n")

            for address, constant in self.constants.items():
                f.write(f"constants,{constant.toCsv()}\n")

            for address in self.newPointersInData:
                f.write(f"new_pointer_in_data,0x{address:08X}\n")

            for address in self.bannedSymbols:
                f.write(f"banned_symbol,0x{address:08X}\n")


    @staticmethod
    def addParametersToArgParse(parser: argparse.ArgumentParser):
        contextParser = parser.add_argument_group("Context configuration")

        contextParser.add_argument("--save-context", help="Saves the context to a file", metavar="FILENAME")


        csvConfig = parser.add_argument_group("Context .csv input files")

        csvConfig.add_argument("--functions", help="Path to a functions csv", action="append")
        csvConfig.add_argument("--variables", help="Path to a variables csv", action="append")
        csvConfig.add_argument("--constants", help="Path to a constants csv", action="append")


        symbolsConfig = parser.add_argument_group("Context default symbols configuration")

        symbolsConfig.add_argument("--default-banned", help="Toggles filling the list of default banned symbols. Defaults to True", action=argparse.BooleanOptionalAction)
        symbolsConfig.add_argument("--libultra-syms", help="Toggles using the built-in libultra symbols. Defaults to True", action=argparse.BooleanOptionalAction)
        symbolsConfig.add_argument("--hardware-regs", help="Toggles using the built-in hardware registers symbols. Defaults to True", action=argparse.BooleanOptionalAction)
        symbolsConfig.add_argument("--named-hardware-regs", help="Use actual names for the hardware registers", action=argparse.BooleanOptionalAction)


    def parseArgs(self, args: argparse.Namespace):
        if args.default_banned != False:
            self.fillDefaultBannedSymbols()
        if args.libultra_syms != False:
            self.fillLibultraSymbols()
        if not args.hardware_regs != False:
            self.fillHardwareRegs(args.named_hardware_regs)

        if args.functions is not None:
            for funcsPath in args.functions:
                self.readFunctionsCsv(funcsPath)
        if args.variables is not None:
            for varsPath in args.variables:
                self.readVariablesCsv(varsPath)
        if args.constants is not None:
            for constantsPath in args.constants:
                self.readConstantsCsv(constantsPath)
