import Classes.DType as Type
import config
from Assembly.CodeBlocks import (createFloatConstant, createIntrinsicConstant,
                                 createStringConstant, extra_parameterlabel,
                                 functionlabel, fncall)
from Assembly.Registers import (norm_parameter_registers,
                                norm_scratch_registers,
                                norm_scratch_registers_inuse, ralloc, rfree,
                                rfreeAll, sse_parameter_registers,
                                sse_scratch_registers,
                                sse_scratch_registers_inuse)
from Classes.Constexpr import buildConstantSet, determineConstexpr
import Classes.ExpressionComponent as EC
from Classes.DType import DType, type_precedence
from Classes.Error import *
from Classes.Location import Location
from Classes.Token import *
from Classes.Token import Token
from Classes.Variable import Variable
from Function import Function
from globals import BOOL, CHAR, DOUBLE, INT, INTRINSICS, LONG, SHORT, VOID, OPERATORS
from Lexer import Lexer
from Structure import Structure
import copy

_asm_extern_indicator = "extern"

#####################################
#
#   The Compiler class is used to compile global variables,
#       isolate structures, and isolate functions.
#   Functions are compiled in the Function class.
#
#   \see Token
#   \see Variable
#   \see Function
#   \see compile.py
#   \see globals
#
######################################

class Compiler:

    def __init__(self):
        self.globals: list = []               # all global variables
        self.constants: str = ""             # raw assembly for constant definitions
        # raw assembly for heap definitions (.bss)
        self.heap: str = ""
        # raw assembly for initialization of heap definitions
        self.initializers: str = ""
        # raw assembly to call the given entrypoint (usually main)
        self.entry: str = ""
        self.text: str = ""                  # raw .text assembly
        self.inittext: str = ""             # raw init assembly
        self.fini: str = ""                 # raw fini assembly
        self.currentfname: str = ""          # current filename
        self.currentTokens: list = []         # current tokens: Token

        self.currentfunction: Function = None     # for fn compiletime

        self.current_token: Token = None       # current token
        self.ctidx: int = 0                  # index of current_token in self.currentTokens
        self.prevtok: Token = Token(T_AMBIGUOUS, T_AMBIGUOUS, None, None)

        self.functions: list = []             # all Function objects
        self.template_functions: list = []    # function templates
        self.templatefunction_cache: list = []  # already created function templates

        self.types: list = []                 # all datatypes: DType
        self.template_types: list = []        # templated types
        self.template_cache: list = []        # already filled templates for speed

        # Store enums:
        # format: {
        #
        #   "<Enum Item Name>" : (DType, value: int)
        #
        # }
        self.enums: dict = {}              # all enums

        # typedefs listed as (old, new):(DType,DType)
        self.tdefs: list = []
        # a hash table for faster access to typedefs
        self.tdef_hash = {}

        for i in INTRINSICS:            # fill types with primitives
            self.types.append(i.copy())

        self.possible_members: list = []

        self.heap_unnamed: int = 0           # int counter for unnamed heap variables

        # an array of all function types (handled differently)
        self.fntypes = []

        # panicmode means continue compiling, but there has already been an
        # error
        self.panicmode: bool = False

    # ensure a semicolon was used, and move on
    def checkSemi(self) -> None:
        if(self.current_token.tok != T_ENDL):
            throw(ExpectedSemicolon(self.currentTokens[self.ctidx - 1]))
        self.advance()

    def isType(self, q: str) -> bool:                # return: if q is type
        return self.getType(
            q) is not None or self.getTemplateType(q) is not None

    def isIntrinsic(self, q: str) -> DType:           # return: if q is primitive
        return next((t for t in INTRINSICS if self.Tequals(t.name, q)), None)

    def ismember(self, q: str) -> bool:
        return q in self.possible_members

    def getGlob(self, q: str) -> Variable:               # get global variable of name q

        # find a variable based on name
        out: Variable = next((g for g in self.globals if g.name == q), None)
        # return out if found
        if(out is not None):
            return out
        # check for functions of that name
        fn: Function = self.getFunction(q)
        # return None if none found
        if(fn is None):
            return None
        # if function is found, find it's corresponding global variable:
        return self.getGlob(fn.getCallingLabel()
                            ) if not fn.extern else self.getGlob(fn.name)

    def getTemplateType(self, q: str) -> DType:
        return next((t for t in self.template_types if t[0].name == q), None)

    def getFunction(self, q: str) -> Function:           # get first function of name q
        return next((f for f in self.functions if f.name == q), None)

    def advance(self):                  # move to next token
        # increment index
        self.ctidx += 1
        # set previous token
        self.prevtok = self.current_token
        try:
            self.current_token = self.currentTokens[self.ctidx]
        except BaseException:
            throw(UnexepectedEOFError(self.current_token))

    def addTdef(self, old, new):
        self.tdefs.append((old, new))
        if old.name in self.tdef_hash:
            self.tdef_hash[old.name].append(new.name)
        else:
            self.tdef_hash[old.name] = [new.name]

    def skipBody(self):
        self.advance()
        opens = 1
        while(opens != 0):
            self.advance()
            if(self.current_token.tok == T_OPENSCOPE):
                opens += 1
            elif(self.current_token.tok == T_CLSSCOPE):
                opens -= 1

    # determine DType equality (including typedefs)
    def Tequals(self, ta: str, tb: str) -> bool:
        # if given names are equal, the types must be
        if(ta == tb):
            return True

        if ta in self.tdef_hash:
            return tb in self.tdef_hash[ta]
        elif tb in self.tdef_hash:
            return ta in self.tdef_hash[tb]

        return False

        # find a typedef pair in the list which equates the two types
        # return next((True for tdef in self.tdefs if (
        #    (tdef[0].name == ta and tdef[1].name == tb) or (tdef[0].name == tb and tdef[1].name == ta))), False)

    def getType(self, qu: str) -> DType:               # get type of name q

        # search types for a datatype that shares a name with the querry
        out: DType = next((t for t in self.types if t.name == qu), None)
        # if none exists, return
        if(out is None):
            return None
        # return safe copy of the type with the restored pointer depth
        out = out.copy()
        return out

    def deriveBaseType(self, t: DType) -> DType:
        for base in self.tdef_hash:
            if t.name not in self.tdef_hash and t.name in self.tdef_hash[base]:
                return self.getType(base)
        return t

    def parseTemplate(self) -> list:
        types = []
        # loop through layer of decorator (' <int, double ... > '  )
        while self.current_token.tok != ">":
            self.advance()
            t: DType = self.checkType()
            t = self.deriveBaseType(t)
            types.append(t)
        return types

    def parseFnDeclParameters(self, checkvarnames=True):
        if self.current_token.tok != "(":
            throw(ExpectedToken(self.current_token, "("))
        self.advance()
        parameters = []
        while self.current_token.tok != T_CLSP:

            t = self.checkType()

            if checkvarnames:
                if(self.current_token.tok != T_ID):
                    throw(ExpectedIdentifier(self.current_token))

                varname = self.current_token.value

                self.advance()
            else:
                varname = "~"

            parameters.append(Variable(t, varname, isptr=t.ptrdepth > 0))
            if (self.current_token.tok == T_CLSP):

                break

            if(self.current_token.tok != T_COMMA):
                throw(ExpectedComma(self.current_token))

            self.advance()

        self.advance()
        return parameters

    def parseFunctionType(self):

        self.advance()
        rett = self.checkType()
        parameters = self.parseFnDeclParameters(False)
        fnout = Function("", parameters, rett, self, [])

        typeout = DType(
            f"function {fnout.createTypename()}",
            8,
            function_template=fnout
        )

        return typeout

    # check next tokens for Type, and return it as a DType
    def checkType(self, err=True):

        # within checkForType, if err is set to True,
        # an error can be thrown on bad syntax or undefined
        # types. When err is not set, None is returned in
        # cases where it would normally throw an error.

        signed = True
        # check for a sign specifier
        if(self.current_token.tok == T_KEYWORD):
            if(self.current_token.value == "unsigned"):
                signed = False
                self.advance()
            elif(self.current_token.value == "signed"):
                self.advance()
            elif (self.current_token.value == "function"):

                return self.parseFunctionType()

        # ensure syntax
        if(self.current_token.tok != T_ID):
            # respond to bad syntax based on err flag
            if err:
                throw(ExpectedIdentifier(self.current_token))
            else:
                return None

        # make sure that the type exists
        if(not self.isType(self.current_token.value)):
            # respond to bad type based on err flag
            if err:
                throw(ExpectedType(self.current_token))
            else:
                return None

        # check for a decorator (template types specifier)
        if (self.currentTokens[self.ctidx + 1].tok == "<"):
            # collect template info:
            template = self.current_token.value
            ttok = self.current_token
            self.advance()
            types = self.parseTemplate()
            # querry compiler for a new type based on template types
            t = self.buildTemplateType(template, types, ttok).copy()
        else:
            # otherwise, querry compiler for a type based on a typename
            t = self.getType(self.current_token.value).copy()

        self.advance()
        # get pointer depth:
        ptrdepth = t.ptrdepth
        while self.current_token.tok == "*":
            ptrdepth += 1
            self.advance()

        # update type properties
        t.ptrdepth = ptrdepth
        t.signed = signed
        return t

    def checkId(self) -> str:
        if self.current_token.tok != T_ID:
            throw(ExpectedIdentifier(self.current_token))
        out = self.current_token.value
        self.advance()
        return out

    # create a constant string value in self.constants
    def createStringConstant(self, content) -> None:

        # d = (.data instructions, varname)
        d: tuple = createStringConstant(content)
        name: str = d[1]
        cnst: str = d[0]
        # add constant info
        self.constants += cnst
        # add new Variable
        v = Variable(CHAR.copy(), name, glob=True)
        self.globals.append(v)

    # create an arbitrary constant in self.constants
    def buildGlobal(self, extern=False) -> None:
        # track start
        startidx = self.ctidx
        # check for a datatype
        intr = self.checkType()
        # check for an identifier
        name = self.checkId()

        # check for simple C style function declarations
        if(self.current_token.tok == T_OPENP or self.current_token.tok == T_NAMESPACE):
            # update indexes, and pass control onto the buildFunction function
            self.ctidx = startidx - 1
            self.advance()
            self.buildFunction()
            return

        # check for multiple declarations
        if self.getGlob(name) is not None:
            fatalThrow(VariableRedeclaration(
                self.currentTokens[self.ctidx - 1], name))

        dtok = self.currentTokens[self.ctidx - 1]

        # variables declared with extern are not placed in the data section, and are simply
        # recorded for use by the compiler.
        if(extern):
            # cannot assign values declared with extern
            if(self.current_token.tok == T_EQUALS):
                throw(AssigningExternedValue(self.current_token))
            # add new variable
            self.globals.append(Variable(intr.copy(), name, glob=True))
            self.globals[-1].dtok = dtok
            # close
            self.checkSemi()
            return

        # test for early endline
        if (self.current_token.tok != T_EQUALS):

            # if there is no assignment, these variables can be moved to the bss section because they
            # are uninitialized.
            if(self.current_token.tok == T_ENDL):
                # create uninilitialized var
                self.globals.append(
                    Variable(
                        intr.copy(),
                        name,
                        glob=True,
                        initializer=0))
                self.globals[-1].dtok = dtok
                # since the var has no initializer, it is stored in the .bss
                # section

                # handle structures
                if not intr.isintrinsic():
                    self.createGlobalStructure(self.globals[-1], dtok, True)
                else:
                    self.heap += f"{name}: resb {intr.csize()}\n"

                # close
                self.advance()
                return
            else:
                # if there is no initializer, there must be a semicolon
                throw(ExpectedValue(self.current_token))

        self.advance()

        # Tokens need to be collected to be passed through the constexpr evaluator.
        # \see Classes.Constexpr

        # tokens representing an expression:
        exprtokens = []
        # determine if the expression is a set literal: (e.g {1,2,3})
        isSet = False
        if(self.current_token.tok == T_OPENSCOPE):
            isSet = True

        # loop through expression tokens until a semicolon
        while(self.current_token.tok != T_ENDL):

            exprtokens.append(self.current_token)
            self.advance()

        # use the constexpr evaluator to find the value for the global
        value: EC.ExpressionComponent = determineConstexpr(intr.isflt(), exprtokens, Function(
            "CMAININIT", [], LONG.copy(), self, exprtokens), True) if not isSet else buildConstantSet(intr.isflt(), exprtokens, Function(
                "CMAININIT", [], LONG.copy(), self, exprtokens))

        isptr = False
        # if the final value is a variable, the initializer to that variable is
        # taken
        if(isinstance(value.accessor, Variable)):
            # value.accessor = value.accessor.name if intr.ptrdepth == value.accessor.t.ptrdepth + \
            #    1 else value.accessor.initializer
            #isptr = True
            if value.accessor.glob and value.accessor.t.ptrdepth > 0:
                value.accessor = value.accessor.name
            elif value.accessor.glob:
                value.accessor = value.accessor.initializer

        # add new Variable
        self.globals.append(Variable(intr.copy(), name,
                                     glob=True, initializer=value.accessor, isptr=isptr))

        self.globals[-1].dtok = dtok

        # add .data instructions to self.constants
        self.constants += createIntrinsicConstant(self.globals[-1])

        if not intr.isintrinsic():
            self.createGlobalStructure(self.globals[-1], dtok, False)

        # close
        self.checkSemi()

    def createGlobalStructure(self, var, dtok, addToHeap, starter=""):
        if addToHeap:

            self.heap += f"{var.name}:\n"
            # implicit constructor
            implicitConstructor = var.t.getConstructor([var.t.up])
            if implicitConstructor is not None:
                self.inittext += f"mov rdi, {var.name}\n{fncall(implicitConstructor)}"
            destructor = var.t.destructor
            if destructor is not None:
                self.fini += f"mov rdi, {var.name}\n{fncall(destructor)}"

        size_added = 0

        for v in var.t.members:
            if isinstance(v.initializer, Function):
                continue
            self.globals.append(
                Variable(v.t, f"{starter}{var.name}.{v.name}", glob=True)
            )
            self.globals[-1].dtok = dtok
            if addToHeap:
                size_added += v.t.csize()
                self.heap += f"{self.globals[-1].name}: resb {v.t.csize()}\n"

        if addToHeap and size_added != var.t.csize():
            self.heap += f"resb {var.t.csize()-size_added}\n"

    # isolate a function and build a Function object

    def buildFunction(self, thisp=False, thispt=None,
                      constructor=False, destructor=False) -> None:
        # track if the function is explicitly inline

        inline = False
        autodecl = False
        operator = False

        # constructor and destructor as special flags set by the Structure class
        # for compiling constructors and destructors
        if not constructor and not destructor:
            # under normal conditions:

            # check for extra info specifier
            if(self.current_token.tok == T_KEYWORD):
                # inline specifier
                if(self.current_token.value == "inline"):
                    inline = True

            # Check for auto specifier
            if self.current_token.tok == T_KEYWORD and self.current_token.value == "auto":
                # handle a function defined with auto
                autodecl = True
                rettype = DType("auto", 8)
                self.advance()

            # normal returntype:
            else:
                # check for a returntype
                rettype = self.checkType()

        # if this is a destructor or constructor:
        else:
            assert thisp
            rettype = VOID.copy()

        # parent structure
        struct = None

        # for external definitions of member functions:
        # (The '::' token will be in place of an '(')
        if(self.currentTokens[self.ctidx + 1].tok == T_NAMESPACE):
            # if(self.current_token.tok != T_ID):
            #    throw(ExpectedIdentifier(self.current_token))
            sname = self.checkId()

            # get parent
            struct = self.getType(sname)

            if(struct is None):
                throw(UnkownType(self.prevtok))

            # setup function for a 'this' value
            thisp = True
            thispt = struct
            # self.advance()
            self.advance()

        # operator specifier
        if(self.current_token.value == "operator"):
            #inline = True
            operator = True
            self.advance()
        # record token that declared the function
        dtok = self.current_token
        if not operator:
            # get fnname
            name = self.checkId()

        else:

            name = self.current_token.value
            if name not in OPERATORS:
                throw(UnkownOperator(name))
            self.advance()
            if name == "[" or name == "(":
                self.advance()

        # ensure syntax
        if(self.current_token.tok != T_OPENP):
            throw(ExpectedParethesis(self.current_token))

        self.advance()

        # construct parameters:
        parameters = []
        # thisp means that this function is a member, and should have 'this' as
        # it's first parameter
        if(thisp):
            parameters.append(
                Variable(
                    thispt.bottom().up(),
                    "this",
                    isptr=True))
        # denoted by '...'
        variardic = False

        # count of each type of parameter
        ssecount = 0
        normcount = int(thisp)

        # load parameters until end of fn header at ')'
        while self.current_token.tok != T_CLSP:
            # check for variardic
            if(self.current_token.tok == T_ELIPSES):
                variardic = True
                self.advance()
                break
            # get parameter type
            t = self.checkType()
            # increment param types
            ssecount += t.isflt()
            normcount += not t.isflt()
            # get parameter name
            varname = self.checkId()

            # add new variable
            parameters.append(Variable(t, varname, isptr=t.ptrdepth > 0))
            parameters[-1].dtok = self.currentTokens[self.ctidx - 1]

            # loop handle:
            if (self.current_token.tok == T_CLSP):

                break
            # ensure syntax
            if(self.current_token.tok != T_COMMA):
                throw(ExpectedComma(self.current_token))

            self.advance()

        self.advance()

        # check for early end (just declaration, no assignment)
        if(self.current_token.tok == T_ENDL):
            self.advance()
            # create empty function for assignment later
            f = Function(
                name,
                parameters,
                rettype,
                self,
                [],
                return_auto=autodecl,
                declare_token=dtok)

            fndtype = DType(
                f"function {f.createTypename()}",
                8,
                function_template=f
            )

            self.globals.append(
                Variable(
                    fndtype,
                    f.getCallingLabel(),
                    glob=True,
                    isptr=True,
                    mutable=False,
                    signed=f.returntype.signed))
            self.globals[-1].dtok = f.declare_token

            self.functions.append(f)

            return
        # if not declaration, it must be an assignment
        if(self.current_token.tok != T_OPENSCOPE):
            throw(ExpectedToken(self.current_token, T_OPENSCOPE))

        self.advance()
        # isolate the body of the function
        # (keep track of scope open / scope close)
        opens = 1
        start = self.ctidx
        # loop through one layer of { ... } scope to
        # catch range of tokens used as function body
        while opens > 0:
            if(self.current_token.tok == T_OPENSCOPE):
                opens += 1
            if(self.current_token.tok == T_CLSSCOPE):
                opens -= 1
            self.advance()

        # construct final object
        f = Function(name, parameters, rettype, self,
                     self.currentTokens[start:self.ctidx], return_auto=autodecl, inline=inline, declare_token=dtok)

        f.unassigned = False

        # pre-compile f to determine it's returntype
        if f.return_auto:
            # track warning settings
            ogwarn = config.__nowarn__
            config.__nowarn__ = True
            # compile f before other functions
            f.compile()
            # restore warning settings
            config.__nowarn__ = ogwarn
            # check if there was a return,
            if f.returntype.name == "auto":
                # if not, the default type is void
                f.returntype = VOID.copy()
            f.isCompiled = False

            rettype = f.returntype

        # setup member fn
        if thisp:
            f.memberfn = True
            f.parentstruct = thispt
        # variardic
        f.variardic = variardic

        # handle additional parameters...
        # the extra parameters needed are any parameters not able to be stored in the
        # SSE registers and the regular registers

        # extra params from sse
        extra_params = (ssecount - len(sse_parameter_registers))
        if extra_params < 0:
            extra_params = 0
        # extra params from regular
        extra_params += (normcount - len(norm_parameter_registers))

        # load info to f
        f.extra_params = extra_params
        f.ssepcount = ssecount
        f.normpcount = normcount

        self.functions.append(f)

        fndtype = DType(
            f"function {f.createTypename()}",
            8,
            function_template=f
        )

        # add as a variable for fn pointers
        self.globals.append(
            Variable(
                fndtype,
                f.getCallingLabel(),
                glob=True,
                isptr=True,
                mutable=False,
                signed=f.returntype.signed))
        self.globals[-1].dtok = f.declare_token

    # isolate and build a structure

    def buildStruct(self, thisp=False, thispt=None,
                    templated=False, tns=None) -> None:
        # \see Structure
        # structure wrapper
        parser = Structure(self, templated, tns)

        try:
            parser.construct()
        except Error as e:
            print(e.__repr__())
            self.skipBody()
            self.advance()
            self.checkSemi()
            self.types.pop()

    def buildEnum(self, thisp=False, thispt=None) -> None:
        pass

    def compile(self, ftup: list) -> None:            # main function to perform Compiler tasks
        self.currentTokens = ftup

        # The first step in compilation is finding all string constants (except inline asm blocks) and float constants
        #   in order to transfer them to the .data section as global variables.

        c = 0
        for t in self.currentTokens:

            # convert string constants to global variables
            if t.tok == T_STRING:
                # preserve assembly blocks
                if(self.currentTokens[c - 2].tok != T_KEYWORD and self.currentTokens[c - 2].value != "__asm"):
                    # construct a string constant
                    data: tuple = createStringConstant(t.value)
                    name: str = data[1]
                    instruct: str = data[0]
                    # get datatype for string
                    tp = CHAR.copy()
                    tp.ptrdepth = 1
                    # build Variable
                    v = Variable(tp, name, glob=True,
                                 isptr=True, initializer=f"`{t.value}`")
                    self.globals.append(v)
                    # update token for later use
                    t.tok = T_ID
                    t.value = name
                    # add allocator to constants
                    #self.constants += instruct

            c += 1

        # reset
        self.current_token = self.currentTokens[0]
        self.ctidx = 0

        # actual compilation step
        while self.current_token.tok != T_EOF:

            self.compileLine()

    def buildTemplate(self):
        # keep track of first token for errors
        first = self.currentTokens[self.ctidx - 1]
        # ensure syntax
        if(self.current_token.tok != "<"):
            throw(ExpectedToken(self.current_token, "<"))

        # collect standin typenames from the template list
        tns = []
        while self.current_token.tok != ">":
            self.advance()
            # expecting a 'struct', 'class', or 'typename'
            if(self.current_token.tok != T_KEYWORD):
                throw(ExpectedToken(self.current_token, "type-specifier"))
            self.advance()
            # check for typename
            tname = self.checkId()
            tns.append(tname)
        self.advance()

        # keep track of the current number of types to restore back to after
        # the template has been created
        restore_types = len(self.types)
        for t in tns:
            self.types.append(DType(t, 0))

        # structs are a simpler process that can be streamlined:
        if(self.current_token.value == "struct"):
            restorefn = len(self.functions)
            self.buildStruct(templated=True, tns=tns)
            self.functions = self.functions[:restorefn]
            #newt = self.types.pop()
            # templated types have their own special list
            # self.template_types[-1].append(tns)

        # functions:
        else:

            # tracker is used to ensure that a function is actually created
            # as this line is compiled
            tracker = len(self.functions)
            self.compileLine()
            if(tracker == len(self.functions)):
                throw(VariableTemplate(first))
            fnt = self.functions.pop()
            # update the created function as a template
            fnt.isTemplate = True
            fnt.template_types = tns
            self.template_functions.append(fnt)

        # delete standin types, and return to original state
        self.types = self.types[:restore_types]

    def buildTemplateType(self, template, types, tok):

        # check for existing templates:
        for t in self.template_cache:
            # they share a name
            if t[0] == template:
                # they have the same number of types
                if len(t[1]) != len(types):
                    break

                # fulleq = ''.join([str(ty) for ty in t[1]]) == ''.join(
                #    [str(ty) for ty in types])
                fulleq = all((t[1][i].__eq__(types[i])
                              for i in range(len(types))))
                if fulleq:
                    return t[2].copy()

        # get the template structure from the list:
        tstruct = self.getTemplateType(template)
        if(tstruct is None):
            throw(UnkownIdentifier(tok))

        # the template structure needs a deep copy of properties and members
        # so that it can be used to instantiate multiple different template
        # structure types
        struct = tstruct[0].copy()

        # deep copy
        for i in range(len(tstruct[0].members)):
            struct.members[i] = tstruct[0].members[i].copy()

        # the second item is an array of typenames used in place of actual types in the
        # structure's definition
        tns = tstruct[1]

        # assosiation is a dictionary that associates the typenames of the template with their
        # actual types given in this declaration
        assosiation = {}
        for i in range(len(tns)):
            assosiation[tns[i]] = types[i].copy()

        # all the members of the new templated type need to be given their new
        # types, and offsets
        struct.s = 0
        struct.name += ''.join([t.name for t in types])
        for member in struct.members:

            # if template has effect:
            if(member.t.name in tns):
                # update type, but maintain pointer depth
                pd = member.t.ptrdepth
                member.t = assosiation[member.t.name].copy()
                member.t.ptrdepth += pd

            if(isinstance(member.initializer, Function)):
                pass
            else:
                # apply offset, and overall size
                member.offset = struct.s
                struct.s += member.t.csize()

        for member in struct.members:
            if(isinstance(member.initializer, Function)):
                member.initializer = member.initializer.deepCopy()
                member.initializer.parameters[0].t = struct.up()
                member.initializer = self.buildTemplateFunction(
                    member.initializer, tns, types)

        for i in range(len(struct.constructors)):
            struct.constructors[i] = struct.constructors[i].deepCopy()
            struct.constructors[i] = self.buildTemplateFunction(
                struct.constructors[i].deepCopy(), tns, types)

        if struct.destructor is not None:
            struct.destructor = struct.destructor.deepCopy()
            struct.destructor = self.buildTemplateFunction(
                struct.destructor, tns, types)

        for op in struct.operators:
            for i in range(len(struct.operators[op])):
                struct.operators[op][i] = struct.operators[op][i].deepCopy()
                struct.operators[op][i].parameters[0].t = struct.up()
                struct.operators[op][i] = self.buildTemplateFunction(
                    struct.operators[op][i], tns, types
                )

        self.template_cache.append([template, types, struct])
        return struct.copy()

    # build / get a template function based on template parameters
    def buildTemplateFunction(self, templatefn, tns, types):
        # restore the types if necessary
        restore_types = len(self.types)
        restore_tdefs = len(self.tdefs)
        restore_tdefhash = copy.deepcopy(self.tdef_hash)

        # create semi-copy function
        fn = Function(
            templatefn.name,
            templatefn.parameters,
            templatefn.returntype,
            self,
            templatefn.tokens,
            templatefn.inline,
            templatefn.extern,
            0,
            templatefn.memberfn,
            templatefn.parentstruct)

        # update returntype standin if necessary
        if fn.returntype.name in tns:
            fn.returntype = types[tns.index(fn.returntype.name)]

        # replace parameter types with their new values if specified in the
        # template
        for i in range(len(fn.parameters)):
            p = fn.parameters[i]
            if p.t.name in tns:
                fn.parameters[i] = fn.parameters[i].copy()
                pd = fn.parameters[i].t.ptrdepth
                fn.parameters[i].t = types[tns.index(p.t.name)].copy()
                fn.parameters[i].t.ptrdepth += pd

            # check for function type parameters using the templated type
            if fn.parameters[i].t.function_template is not None:
                fn.parameters[i].t.function_template = self.buildTemplateFunction(fn.parameters[i].t.function_template, tns, types)

        # check if the function has already been built before
        fnexist = templatefn.getFunction(
            fn.name, [p.t for p in fn.parameters], fn.returntype, loose=False)

        # if it has been built, just use the existing build
        if(fnexist is not None):
            fn = fnexist
        else:
            # if it's not been built, the new temporary types need to be added
            # to compile the template function.
            for i in range(len(tns)):
                temptype = types[i].copy()
                temptype.name = tns[i]
                self.types.append(temptype)
                self.tdefs.append((types[i], temptype))
                if types[i].name in self.tdef_hash:
                    self.tdef_hash[types[i].name].append(temptype.name)
                else:
                    self.tdef_hash[types[i].name] = [temptype.name]

        # if it is not already built, it needs to be compiled
        if not fn.isCompiled:

            # compile
            fn.compile()
            # save
            self.functions.append(fn)
            # update
            fn.isCompiled = True
            fn.isTemplate = True
            if not fn.inline:
                self.text = f"{fn.asm}{self.text}"

        # restore types if necessary
        self.types = self.types[:restore_types]
        self.tdefs = self.tdefs[:restore_tdefs]
        self.tdef_hash = restore_tdefhash
        return fn

    # unsigned keyword is always followed by a normal variable
    # declaration.
    def buildUnsigned(self, thisp=False, thispt=None) -> None:
        s = self.current_token
        self.advance()
        self.buildGlobal()
        v = self.globals[-1]
        if(v.isflt()):
            throw(InvalidSignSpecifier(s))

        v.signed = False
        v.t = v.t.copy()
        v.t.signed = False
        v.glob = True

    def buildSafetype(self, thisp=False, thispt=None) -> None:
        self.advance()
        tat = self.current_token
        typeAName = self.checkType()
        typeBName = self.checkId()
        typeA = self.getType(typeAName)
        if typeA is None:
            fatalThrow(ExpectedType(tat))
        newtype = typeA.copy()
        newtype.name = typeBName
        self.types.append(newtype)

        self.checkSemi()

    # typedef is always followed by two types and an endline:
    def buildTypedef(self, thisp=False, thispt=None) -> None:

        # start token
        s = self.current_token

        self.advance()

        # type a
        ta = self.checkType()
        if(self.current_token.tok != T_ID):
            throw(ExpectedIdentifier(self.current_token))

        # check for layered typedefs
        if ta.name in self.tdef_hash:
            ogt = self.tdef_hash[ta.name][0]
            if ogt in ['char', 'short',
                       'int', "long", "float",
                       "double", "void"]:
                ta.name = ogt

        # new type name
        ntn = self.current_token.value
        newtype = ta.copy()
        newtype.name = ntn

        # add new typename to precedence list, if applicable
        if ta.name in type_precedence:
            type_precedence[ntn] = type_precedence[ta.name]

        # add new type to types and tdefs
        self.types.append(newtype.copy())
        self.tdefs.append((ta, newtype))

        # setup hash table for fast access
        if ta.name in self.tdef_hash:
            self.tdef_hash[ta.name].append(newtype.name)
        else:
            self.tdef_hash[ta.name] = [newtype.name]

        if newtype.name in self.tdef_hash:
            self.tdef_hash[newtype.name].append(ta.name)
        else:
            self.tdef_hash[newtype.name] = [ta.name]

        if(self.isIntrinsic(ntn)):
            INTRINSICS.append(newtype.copy())
        self.advance()
        self.checkSemi()

    def determineFunctionOrVar(self) -> None:
        self.advance()
        # record location to jump back to
        backto = self.ctidx - 1
        self.checkType()
        # account for operator overloads
        if self.current_token.tok == T_KEYWORD and self.current_token.value == 'operator':
            self.advance()

        self.advance()

        # function determinant:
        #   for function delcarations fndp.tok will always be a '(', and for variables
        #   it will always be something else.
        fndp = self.current_token

        # with the determinant, jump back to the begining to
        # perform the compilation
        self.ctidx = backto
        self.advance()

        return fndp

    # extern is followed by either a function declaration or a
    # variable declaration
    def buildExtern(self, thisp=False, thispt=None) -> None:
        # function determinant
        fndp = self.determineFunctionOrVar()

        if(fndp.tok == "(" or fndp.tok == T_NAMESPACE):  # if is function

            self.buildFunction(thisp=thisp, thispt=thispt)
            fn = self.functions[-1]
            config.__CEXTERNS__ += f"{_asm_extern_indicator} " + \
                functionlabel(fn)[:-1] + "\n"
            glob = self.globals[-1]
            glob.name = fn.getCallingLabel()

        else:  # if is variable

            self.buildGlobal(True)

            config.__CEXTERNS__ += f"{_asm_extern_indicator} " + \
                self.globals[-1].name + "\n"

    # same code as extern, with slight modification for cextern
    def buildCextern(self, thisp=False, thispt=None) -> None:
        fndp = self.determineFunctionOrVar()

        if(fndp.tok == "(" or fndp.tok == T_NAMESPACE):

            self.buildFunction(thisp=thisp, thispt=thispt)
            fn = self.functions[-1]
            fn.extern = True
            config.__CEXTERNS__ += f"{_asm_extern_indicator} " + \
                functionlabel(fn)[:-1] + "\n"
            glob = self.globals[-1]
            glob.name = fn.getCallingLabel()

        else:

            self.buildGlobal(True)
            config.__CEXTERNS__ += f"{_asm_extern_indicator} " + \
                self.globals[-1].name + "\n"
    # __cdecl is always followed by a function declaration

    def buildCdecl(self, thisp=False, thispt=None) -> None:
        self.advance()

        self.buildFunction(thisp=thisp, thispt=thispt)
        fn = self.functions[-1]
        fn.extern = True
        # apply new properties to generated function:
        config.__CEXTERNS__ += "global " + \
            functionlabel(fn)[:-1] + "\n"
        glob = self.globals[-1]
        glob.name = fn.getCallingLabel()
    # global is always followed by a function declaration

    def buildGlobalfn(self, thisp=False, thispt=None) -> None:
        self.advance()

        self.buildFunction(thisp=thisp, thispt=thispt)
        # apply global properties
        fn = self.functions[-1]
        config.__CEXTERNS__ += "global " + \
            functionlabel(fn)[:-1] + "\n"
        glob = self.globals[-1]
        glob.name = fn.getCallingLabel()

    # inline is always followed by a function declaration
    def buildInlinefn(self, thisp=False, thispt=None) -> None:
        self.advance()
        self.buildFunction(thisp=thisp, thispt=thispt)
        # apply new properties
        if(not config.__Osize__):
            self.functions[-1].inline = True
            self.globals.pop()
        else:
            self.functions[-1].wouldbe_inline = True

    def buildNormalfn(self, thisp=False, thispt=None) -> None:
        self.advance()
        self.buildFunction(thisp=thisp, thispt=thispt)

    def buildWinextern(self, thisp=False, thispt=None) -> None:
        self.buildCextern(thisp, thispt)
        self.functions[-1].winextern = True

    def buildAutofn(self, thisp=False, thispt=None) -> None:
        self.buildFunction(thisp=thisp, thispt=thispt)

    def beginTemplate(self, thisp=False, thispt=None) -> None:
        self.advance()
        self.buildTemplate()

    # compileLine is responsible for determining the categorie of a general unscoped statement.
    # e.g: unsigned int i = 0;
    # e.g: int main(int argc) {}
    # And then either compiling the line if its just a global variable, or sectioning it off to
    # be handled by the Structure element or the Function element for more
    # complex statements.
    def compileLine(self, thisp=False, thispt=None):
        # lines begining with an ID will be a global variable
        # (Or function, but that is handled in createConstant())
        if (self.current_token.tok == T_ID):
            self.buildGlobal()
        elif (self.current_token.tok == T_KEYWORD):

            # check if a response exists for given keyword
            if self.current_token.value in keyword_responses:
                # execute respnse
                keyword_responses[self.current_token.value](
                    self, thisp, thispt)
            else:
                # if not, throw error
                throw(UnexpectedToken(self.current_token))

        else:
            throw(UnexpectedToken(self.current_token))

    def verify_entrypoint(self, f):
        # check returntype
        if f.returntype.isflt():
            print(InvalidMainReturn(f.declare_token).__repr__())
            self.panicmode = True

        # check valid parameters
        if len(f.parameters) > 0:
            # check parameter 1
            if not (f.parameters[0].t.__eq__(INT)
                    or f.parameters[0].t.__eq__(LONG)):
                warn(InvalidMainParameters(f.parameters[0].dtok))
            # check for parameter 2
            if len(f.parameters) > 1:
                # check the parameter 2 type
                if not (f.parameters[1].t.__eq__(CHAR.up().up())):
                    warn(InvalidMainParameters(f.parameters[1].dtok))

    # compile all functions and fill in raw assembly info

    def finalize(self):

        # the Compiler needs to find the best suitable entrypoint.
        # The first function called main will be used, reguardless of
        # returntype, or parameters.
        for f in self.functions:
            if f.name == "main":

                self.verify_entrypoint(f)

                self.entry = f

                f.extern = True
                f.winextern = True

                self.globals.append(
                    Variable(
                        DType(
                            f"function {f.createTypename()}",
                            8,
                            function_template=f
                        ),
                        "main",
                        glob=True,
                        initializer=f,
                        isptr=True,
                    )
                )
                self.globals[-1].dtok = f.declare_token

        # at this point all functions exist as Function objects, but have not
        # been compiled into asm.
        for f in (self.functions):

            #   TODO: Set up some size optimizations
            # if(config.__Osize__):
            #    if f.wouldbe_inline and f.references == 0:
            #        f.inline = True

            if not f.inline and not f.isTemplate:

                self.currentfunction = f

                # catch errors to continue compilation
                try:

                    f.compile()

                except Error as e:
                    # assuming the error is non fatal:
                    print(e.__repr__())
                    rfreeAll()
                    continue

                if(True in norm_scratch_registers_inuse or True in sse_scratch_registers_inuse):
                    print(
                        f"Warning:\n\tRegister leak of degree {norm_scratch_registers_inuse.count(True)+sse_scratch_registers_inuse.count(True)} found in function:\n\t {f}\n\t called from: {config.LAST_RALLOC}\n")
                rfreeAll()  # make sure there are no register leaks between functions

                # add comment
                if(config.DO_DEBUG):
                    f.asm = f"\n\n\n;{f.__repr__()}\n\n\n\n\n{f.asm}"

                self.text = f"{self.text}{f.asm}"

                # garbage collection
                f.GC()

        for v in self.globals:
            if v.name.startswith("__LC.S"):
                self.constants += f"{v.name}: db {v.initializer}, 0\n"

# Keyword responses outlines the functions that the compiler will use for a given
# keyword.

keyword_responses = {
    "unsigned": Compiler.buildUnsigned,
    "typedef": Compiler.buildTypedef,
    "extern": Compiler.buildExtern,
    "cextern": Compiler.buildCextern,
    "__cdecl": Compiler.buildCdecl,
    "global": Compiler.buildGlobalfn,
    "struct": Compiler.buildStruct,
    "inline": Compiler.buildInlinefn,
    "function": Compiler.buildNormalfn,
    "winextern": Compiler.buildWinextern,
    "template": Compiler.beginTemplate,
    "auto": Compiler.buildAutofn,
    "enum": Compiler.buildEnum,
    "class": Compiler.buildStruct,
    "safetype": Compiler.buildSafetype
}


"""
주소: https://leetcode.com/problems/design-twitter/

내용
- 트위터 로직을 재현해보자
- 트위터의 다음 기능을 구현하라
  - 트윗 포스트: 새로운 트윗을 올림
  - 뉴스피드 가져오기: 자기 자신과 자기가 팔로우하는 사람의 최근 트윗 10개를 가져오기
  - 팔로우: 소식을 받아볼 사람 등록
  - 언팔로우: 소식을 받아볼 사람 삭제

예제
Example 1:
Input
["Twitter", "postTweet", "getNewsFeed", "follow", "postTweet", "getNewsFeed", "unfollow", "getNewsFeed"]
[[], [1, 5], [1], [1, 2], [2, 6], [1], [1, 2], [1]]
Output
[null, null, [5], null, null, [6, 5], null, [5]]

Explanation
Twitter twitter = new Twitter();
twitter.postTweet(1, 5); // User 1 posts a new tweet (id = 5).
twitter.getNewsFeed(1);  // User 1's news feed should return a list with 1 tweet id -> [5]. return [5]
twitter.follow(1, 2);    // User 1 follows user 2.
twitter.postTweet(2, 6); // User 2 posts a new tweet (id = 6).
twitter.getNewsFeed(1);  // User 1's news feed should return a list with 2 tweet ids -> [6, 5]. Tweet id 6 should precede tweet id 5 because it is posted after tweet id 5.
twitter.unfollow(1, 2);  // User 1 unfollows user 2.
twitter.getNewsFeed(1);  // User 1's news feed should return a list with 1 tweet id -> [5], since user 1 is no longer following user 2.

풀이방법
- 단순 구현
- 포스트는 리스트, 팔로워는 set의 map으로 관리
"""
class Twitter:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.posts = []
        self.follows = dict()

    def postTweet(self, userId: int, tweetId: int) -> None:
        """
        Compose a new tweet.
        """
        self.posts.append([userId, tweetId])

    def getNewsFeed(self, userId: int) -> List[int]:
        """
        Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
        """
        cnt = 0
        idx = 0
        res = []

        while cnt < 10 and idx < len(self.posts):
            cur_post = self.posts[-(idx + 1)]
            if cur_post[0] == userId or (userId in self.follows and cur_post[0] in self.follows[userId]):
                res.append(cur_post[1])
                cnt += 1
            idx += 1

        return res

    def follow(self, followerId: int, followeeId: int) -> None:
        """
        Follower follows a followee. If the operation is invalid, it should be a no-op.
        """
        if followerId in self.follows:
            self.follows[followerId].add(followeeId)
        else:
            self.follows[followerId] = set([followeeId])

    def unfollow(self, followerId: int, followeeId: int) -> None:
        """
        Follower unfollows a followee. If the operation is invalid, it should be a no-op.
        """
        if followerId in self.follows:
            self.follows[followerId].remove(followeeId)

# Your Twitter object will be instantiated and called as such:
# obj = Twitter()
# obj.postTweet(userId,tweetId)
# param_2 = obj.getNewsFeed(userId)
# obj.follow(followerId,followeeId)
# obj.unfollow(followerId,followeeId)


# !/usr/bin/python
# -*- coding: utf-8 -*-
from shoutout import shout_out
from scorecalculate import socre_calculate

# 每个玩家的初始分数为100
score = {'com1_score': 100, 'com2_score': 100, 'com3_score': 100, 'player_score': 100}
# 定义玩家的姓名
p = {'com1_name', 'com2_name', 'com3_name', 'player_name'}

# 通过判断玩家的姓名来决定哪个是玩家哪个是电脑，如果是电脑则采用生成，如果是玩家则从【鸡，老虎，棒子】中选择输入
def shout_one_round(users):
    shout_dic = {}
    for name in users:
        if 'com1' in name:
            com1_code = shout_out.shout_out('suiji')
            shout_dic.update({'com1': com1_code})
        elif 'com2' in name:
            com2_code = shout_out.shout_out('suiji')
            shout_dic.update({'com2': com2_code})
        elif 'com3' in name:
            com3_code = shout_out.shout_out('suiji')
            shout_dic.update({'com3': com3_code})
        elif 'player' in name:
            # 如果是玩家的话，则由玩家自行出拳，玩家只能输入鸡，老虎，棒子，否则输入无效
            while True:
                player_code_in = input('请出拳（只能输入鸡，老虎，棒子）：')
                if '鸡' == player_code_in:
                    player_code = shout_out.shout_out(player_code_in)
                    shout_dic.update({'player': player_code})
                    break
                elif '老虎' == player_code_in:
                    player_code = shout_out.shout_out(player_code_in)
                    shout_dic.update({'player': player_code})
                    break
                elif '棒子' == player_code_in:
                    player_code = shout_out.shout_out(player_code_in)
                    shout_dic.update({'player': player_code})
                    break
                else:
                    print('您的输入无效，请重新输入！')
        else:
            break
    return shout_dic

def play_one_round(players, s):
    # 用列表存储最终的结果，0表示平局，非平局时返回获胜玩家
    result_list = []
    win_code_list = []
    # 判断玩家数量，如果大于1，则可以继续进行游戏
    if len(players) > 1:
        # 根据传入的玩家数据，算出各个玩家所喊的内容
        shout_result = shout_one_round(players)
        # 将产生的结果去重统计数量
        func = lambda z: dict([x, y] for y, x in z.items())
        shout_result_f = func(shout_result)
        # 如果去重后的数组的个数为1或者3个，则此局打成平手
        if len(set(shout_result_f)) == 1 or len(set(shout_result_f)) == 3:
            # 打成平手时，往结果数组内，加入一个0，然后调用平局时计算分数的函数并返回最终分数
            result_list.append(0)
            socre_calculate.score_cal_tie(s)
            return players, s, result_list, shout_result
        # 如果去重后的结果列表个数为2个，则需计算胜负
        elif len(set(shout_result_f)) == 2:
            # 定义一个列表来存储去重后的结果，只取key，即所喊的内容
            code_list_key = list()
            for key in shout_result_f.keys():
                code_list_key.append(key)
            # 将去重后所喊的结果分别存在code1和code2
            code1 = code_list_key[0]
            code2 = code_list_key[1]
            # 根据code1及code2的差值比较最终赢家
            code_compare = code1 - code2
            # 当code_compare等于1或者-2时，则code1为获胜方，code2为输的一方
            if code_compare == 1 or code_compare == -2:
                # 获取字典中对应value的key值，
                code1_list = return_key(code1, shout_result)
                code2_list = return_key(code2, shout_result)
                result_list = code1_list
                # win_code_list = code1_list
                # 根据输赢结果计算此局游戏各个玩家的最终分数
                s1 = socre_calculate.score_cal_win(code1_list, s)
                s = socre_calculate.score_cal_loss(code2_list, s1)
                # 判断玩家的分数，若分数为0，则玩家被从列表移除
                for key in list(s.keys()):
                    if s[key] == 0 or s[key] < 0:
                        if 'com1' in key:
                            print('com1分数耗尽，退出了游戏。')
                            players.remove('com1_name')
                            s.pop('com1_score')
                        elif 'com2' in key:
                            print('com2分数耗尽，退出了游戏。')
                            players.remove('com2_name')
                            s.pop('com2_score')
                        elif 'com3' in key:
                            print('com3分数耗尽，退出了游戏。')
                            players.remove('com3_name')
                            s.pop('com3_score')
                        elif 'player' in key:
                            print('您的分数为0，无法继续游戏，请等待决出胜负！！')
                            players.remove('player_name')
                            s.pop('player_score')
                return players, s, result_list, shout_result
            elif code_compare == -1 or code_compare == 2:
                # 当code_compare==2或者-1时，code1为输的一方，code2为胜方
                code1_list = return_key(code1, shout_result)
                code2_list = return_key(code2, shout_result)
                result_list = code2_list
                # win_code_list = code2_list
                # 根据输赢结果计算此局游戏各个玩家的最终分数
                s1 = socre_calculate.score_cal_win(code2_list, s)
                s = socre_calculate.score_cal_loss(code1_list, s1)
                # 判断玩家的分数，若分数为0，则玩家被从列表移除
                for key in list(s.keys()):
                    if s[key] == 0 or s[key] < 0:
                        if 'com1' in key:
                            print('com1分数耗尽，退出了游戏。')
                            players.remove('com1_name')
                            s.pop('com1_score')
                        elif 'com2' in key:
                            print('com2分数耗尽，退出了游戏。')
                            players.remove('com2_name')
                            s.pop('com2_score')
                        elif 'com3' in key:
                            print('com3分数耗尽，退出了游戏。')
                            players.remove('com3_name')
                            s.pop('com3_score')
                        elif 'player' in key:
                            print('您的分数为0，无法继续游戏，请等待决出胜负！！')
                            players.remove('player_name')
                            s.pop('player_score')
                return players, s, result_list, shout_result
    else:
        print('游戏结束啦')

# 获取字典中对应value所对应的key
def return_key(val, curr_dic):
    key_list = []
    for k, v in curr_dic.items():
        if v == val:
            key_list.append(k)
    return key_list

if __name__ == '__main__':
    shout_one_round(p)
    play_one_round(p, score)


#Henrique Shigihara
#IPL-2021
#Problema 1.4.1: Cálculo Polinomial 1) Derivadas

poly = [0, 0, 1/2]
out = []

if poly != []:
    for i in range(len(poly)):
        if i > 0:
            res = poly[i] * (i+1)
            res = int(res)
            out.append(res)
else:
    out = None

print(out)


import os
import json
import requests
import ibm_boto3
from ibm_botocore.client import Config, ClientError

try:
    _credentials_string = os.environ.get('IBM_CLOUD_STO_CREDENTIALS')
    print('credentials :: ', _credentials_string)
    cos_service_credentials = json.loads(_credentials_string)

    # Request detailed enpoint list
    endpoints = requests.get(cos_service_credentials['endpoints']).json()

    # setup other details
    iam_host = (endpoints['identity-endpoints']['iam-token'])
    cos_host = (endpoints['service-endpoints']['regional']['us-south']['public']['us-south'])
    auth_endpoint = "https://" + iam_host + "/oidc/token"
    service_endpoint = "https://" + cos_host

    # Create resource
    cos = ibm_boto3.resource("s3",
        ibm_api_key_id=cos_service_credentials['apikey'],
        ibm_service_instance_id=cos_service_credentials['resource_instance_id'],
        ibm_auth_endpoint=auth_endpoint,
        config=Config(signature_version="oauth"),
        endpoint_url=service_endpoint
    )
except:
    print('unable to create cos')

def get_item(bucket_name, item_name):
    print("Retrieving item {0} from bucket {1}".format(item_name, bucket_name))
    try:
        obj = cos.Object(bucket_name, item_name).get()
        return obj['Body'].read()
    except ClientError as be:
        print("CLIENT ERROR: {0}\n".format(be))
        return False
    except Exception as e:
        print("Unable to retrieve file contents: {0}".format(e))
        return False


import sys
import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gi.repository.GLib
import argparse

def signal_handler(message):
    try:
        print(message)
        sys.stdout.flush()
    except:
        loop.quit()

loop = None

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='stdin to dbus')
    parser.add_argument('dbus_interface_name', type=str)
    args = parser.parse_args()

    arg_dbus_interface_name = args.dbus_interface_name

    DBusGMainLoop(set_as_default=True)

    bus = dbus.SessionBus()
    bus.add_signal_receiver(signal_handler,dbus_interface=arg_dbus_interface_name)

    loop = gi.repository.GLib.MainLoop()
    loop.run()


import os
import re
import traceback
from datetime import datetime
from uuid import uuid4

from fastapi import Request, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse

from .app import app
from .items import Item_jzmh
from .open_search import client, index_name
from .encode import encode
from .exact_sim import exact_sim
from .jzmh import send_message

def get_answer(question, token, size=5):
    hits = client.search(index=index_name, body={
        "_source": [
            "question",
            "answer",
            "parent",
            "url",
            "created_at",
            "updated_at"
        ],
        "query": {
            "bool": {
                "filter": [ # 这里用filter，避免计入分数
                    {
                        "term": {
                            "token": token
                        }
                    }
                ],
                "should": [
                    {
                        "knn": {
                            "question_vec": {
                                "vector": encode(question),
                                "k": 10,
                            }
                        }
                    }
                ]
            }
        },
        "size": size
    })
    rets = []
    for obj in hits['hits']['hits']:
        s = obj['_source']
        s['id'] = obj['_id']
        s['score'] = obj['_score']
        s['exact_score'] = exact_sim(question, s['question'])
        s['score'] = (s['score'] + s['exact_score']) / 2
        rets.append(obj['_source'])
    rets = sorted(rets, key=lambda x: x['score'], reverse=True)
    return rets

def make_answer(rets):
    if len(rets) <= 0:
        return ''
    if rets[0]['score'] > 0.8 or rets[0]['exact_score'] > 0.5:
        a = rets[0]
        text = f'''{a["question"]}
{a["answer"]}'''
    else:
        text = f'''没有找到精确匹配的回答，你问的是下面几个问题吗？

{rets[0]["question"].strip()}'''
        if len(rets) > 1:
            text += f'\n{rets[1]["question"].strip()}'
        if len(rets) > 2:
            text += f'\n{rets[2]["question"].strip()}'
    return text

@app.post('/api/qabot/ask/{token}')
async def api_qabot_ask(token: str, request: Request):
    """提问

    curl localhost:8000/api/qabot/ask/test_token \
        -H 'Content-Type: application/json' \
        -d '{"question": "吃了"}'
    """
    body = await request.json()
    question = body.get('question')
    rets = get_answer(question, token)

    return {
        'ok': True,
        'data': rets,
        'text': make_answer(rets)
    }

@app.get('/api/qabot/pair/{token}')
@app.get('/api/qabot/pair/{token}/{page}/{page_size}')
async def api_qabot_post(token: str='', page: int=0, page_size: int=10):
    """获取

    curl localhost:8000/api/qabot/pair/test_token
    """
    hits = client.search(index=index_name, body={
        "_source": [
            "question",
            "answer",
            "parent",
            "url",
            "created_at",
            "updated_at"
        ],
        "query": {
            "term": {
                "token": token
            }
        },
        "from": page * page_size,
        "size": page_size,
        "track_total_hits": True,
        "sort" : [
            { "updated_at" : "desc" }
        ]
    })
    total = hits['hits']['total']['value']
    rets = []
    for obj in hits['hits']['hits']:
        obj['_source']['id'] = obj['_id']
        rets.append(obj['_source'])
    return {
        'ok': True,
        'data': rets,
        'total': total,
    }

@app.post('/api/qabot/pair/{token}')
async def api_qabot_post(token: str, request: Request):
    """添加

    curl -XPOST localhost:8000/api/qabot/pair/test_token \
        -H 'Content-Type: application/json' \
        -d '{"question": "吃了吗", "answer": "没吃呢", "url": ""}'
    """
    if not isinstance(token, str) or len(token) <= 0:
        return {
            'ok': False,
            'error': 'Invalid token'
        }
    body = await request.json()
    question = body.get('question')
    answer = body.get('answer')
    parent = body.get('parent')
    url = body.get('url')
    if not isinstance(question, str):
        return {
            'ok': False,
            'error': 'Invalid question'
        }
    if not isinstance(answer, str):
        return {
            'ok': False,
            'error': 'Invalid answer'
        }

    hits = client.search(index=index_name, body={
        '_source': ['question'],
        'query': {
            'bool': {
                'filter': {
                    'term': {
                        'token': token
                    }
                },
                'must': {
                    'term': {
                        'question.keyword': question
                    }
                }
            }
        }
    })

    _id = str(uuid4())
    exists = False
    if hits['hits']['total']['value'] > 0:
        _id = hits['hits']['hits'][0]['_id']
        exists = True

    now = str(datetime.now()).replace(' ', 'T')[:19]
    ret = client.index(index=index_name, id=_id, body={
        'token': token,
        'question': question,
        'answer': answer,
        'parent': parent,
        'url': url,
        'question_vec': encode(question),
        'created_at': now,
        'updated_at': now,
    }, refresh='wait_for')
    client.indices.refresh(index_name)
    print('create', ret)
    if ret.get('result') == 'created':
        return {
            'ok': True,
            'exists': exists,
        }
    else:
        return {
            'ok': False,
            'error': '未知错误',
            'exists': exists,
        }

@app.put('/api/qabot/pair/{token}/{_id}')
async def api_qabot_put(token: str, _id: str, request: Request):
    """修改

    curl -XPUT localhost:8000/api/qabot/pair/test_token \
        -H 'Content-Type: application/json' \
        -d '{"id": "930ab216-706e-4e64-a08c-16703f5ad3ee", "question": "吃了吗", "answer": "没吃呢啊", "url": ""}'
    """
    if not isinstance(token, str) or len(token) <= 0:
        return {
            'ok': False,
            'error': 'Invalid token'
        }
    body = await request.json()
    question = body.get('question')
    answer = body.get('answer')
    parent = body.get('parent')
    url = body.get('url')
    if not isinstance(_id, str):
        return {
            'ok': False,
            'error': 'Invalid id'
        }
    if not isinstance(question, str):
        return {
            'ok': False,
            'error': 'Invalid question'
        }
    if not isinstance(answer, str):
        return {
            'ok': False,
            'error': 'Invalid answer'
        }
    now = str(datetime.now()).replace(' ', 'T')[:19]
    ret = client.update(index=index_name, id=_id, body={
        'doc': {
            'question': question,
            'answer': answer,
            'parent': parent,
            'url': url,
            'question_vec': encode(question),
            'updated_at': now,
        }
    }, refresh='wait_for')
    client.indices.refresh(index_name)
    print(ret)
    return {
        'ok': True,
    }

@app.delete('/api/qabot/pair/{token}/{_id}')
async def api_qabot_delete(token: str='', _id: str=''):
    """删除
    curl -XDELETE localhost:8000/api/qabot/pair/test_token/930ab216-706e-4e64-a08c-16703f5ad3ee
    """
    # 删除问题
    ret = client.delete(index=index_name, id=_id)
    print(ret)
    # 删除子问题
    client.delete_by_query(index=index_name, body={
        "query": {
            "bool": {
                "filter": [
                    {
                        "term": {
                            "token": token
                        }
                    },
                    {
                        "term": {
                            "parent": _id
                        }
                    }
                ]
            }
        }
    })
    client.indices.refresh(index_name)
    # client.cluster.health(wait_for_no_relocating_shards=True, wait_for_active_shards='all')
    print('delete', ret)
    if ret.get('result') == 'deleted':
        return {
            'ok': True,
        }
    else:
        return {
            'ok': False,
            'error': '未知错误',
        }

@app.post("/api/qabot/message")
async def receive_message(item: Item_jzmh, request: Request):
    """对接句子互动的api
    curl -XPOST localhost:1333/api/qabot/message -H 'Content-Type: application/json' \
        -d '{
            "data": { "payload": { "text": "hello" }, "chatId": "fake" }
        }'
    """
    print('receive message', datetime.now(), f'"{item.data.payload.text}"')
    token = item.data.token
    question = item.data.payload.text
    # 去除群聊的at信息
    if question.startswith('@'):
        question = re.sub(r'^@[^\s]+\s', '', question)
    rets = get_answer(question, token)
    text = make_answer(rets)
    mention = []
    is_group = not not item.data.roomId
    if is_group:
        mention = [item.data.contactId]
    ret = send_message(chatid=item.data.chatId, text=text, token=token, mention=mention)
    print('response message', datetime.now())
    return ret

@app.get('/')
async def hello_world():
    return {'hello': 'world'}


# -*- coding: utf-8 -*-
from interlegis.portalmodelo.api import _
from Products.CMFPlone.interfaces import INonInstallable
from zope.interface import implements
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary

PROJECTNAME = 'interlegis.portalmodelo.api'

REGISTER_URL = 'https://www.interlegis.leg.br/registrar?produto=portalmodelo&versao=3.0'

class HiddenProfiles(object):
    implements(INonInstallable)

    def getNonInstallableProfiles(self):
        return [
            u'interlegis.portalmodelo.api:uninstall',
            u'interlegis.portalmodelo.api.upgrades.v1010:default'
        ]

KINDS = SimpleVocabulary([
    SimpleTerm(value=u'assembleia-legislativa', title=_(u'Assembleia Legislativa')),
    SimpleTerm(value=u'camara-dos-deputados', title=_(u'Câmara dos Deputados')),
    SimpleTerm(value=u'camara-legislativa', title=_(u'Câmara Legislativa')),
    SimpleTerm(value=u'camara-municipal', title=_(u'Câmara Municipal')),
    SimpleTerm(value=u'outro', title=_(u'Outro')),
    SimpleTerm(value=u'senado-federal', title=_(u'Senado Federal')),
    SimpleTerm(value=u'tribunal-de-contas-da-uniao', title=_(u'Tribunal de Contas da União')),
    SimpleTerm(value=u'tribunal-de-contas-estadual', title=_(u'Tribunal de Contas Estadual')),
    SimpleTerm(value=u'tribunal-de-contas-municipal', title=_(u'Tribunal de Contas Municipal')),
])


# -*- coding: utf-8 -*-
import os
import logging
import json
from datetime import datetime
import requests

from dotenv import load_dotenv

import azure.functions as func

def main(req: func.HttpRequest) -> func.HttpResponse:
    # On charge les variables de l'application
    load_dotenv()
    model_aci_url = os.getenv("MODEL_ACI_URL")

    logging.info(f"Python HTTP trigger function processed a request at : {model_aci_url}")

    # On récupère l'id de l'utilisateur
    user_id = req.params.get("userId")
    if not user_id:
        try:
            data = req.get_json()
            user_id = data.get("userId")
        except ValueError:
            return func.HttpResponse(
                "Il manque le paramètre 'userId'",
                status_code=200
            )

    # On crée la donnée d'entrée du modèle déployé
    data = {
        "user_id": str(user_id),
        "session_start_dt": datetime(2017, 10, 16, 12).isoformat(),
        "top_n": 5
    }

    # On appelle le service ACI pour effectuer la prédiction des recommendations
    r = requests.post(model_aci_url, json=data)

    # On vérifie la réponse
    if not r.ok:
        return func.HttpResponse(
             f"Erreur du service ACI {r.status_code}",
             status_code=200
        )

    article_ids = r.json().get("article_ids", [0] * 5)

    logging.info(f"Article ids for user {user_id} : {article_ids}")

    # On spécifie que la réponse sera en JSON
    func.HttpResponse.mimetype = "application/json"
    func.HttpResponse.charset = "utf-8"

    return func.HttpResponse(json.dumps(article_ids))


"""
-*- coding: utf-8 -*-
@File  : metrics.py
@Author: 钟世杰
@Date  : 2023/3/6
@Desc  :
@Contact : zhongshijie1995@outlook.com
"""
from typing import Any, Dict

import lightgbm as lgb
import numpy as np
import pandas as pd
from loguru import logger
from sklearn.metrics import confusion_matrix, f1_score

class Metrics:

    @staticmethod
    def trans_pred(_y_pred_prob: Any, threshold: float = 0.5) -> pd.Series:
        """
        根据阈值划分0-1

        :param _y_pred_prob: 预测概率
        :param threshold: 阈值

        :return: 预测分类
        """
        _y_pred = np.minimum(np.round(_y_pred_prob + (0.5 - threshold)), 1)
        return _y_pred

    @staticmethod
    def f1_score_self(_y_true, _y_pred) -> float:
        tn, fp, fn, tp = confusion_matrix(y_true=_y_true, y_pred=_y_pred).ravel()
        # tp_rate = tp / (tp + fn)
        # fp_rate = fp / (fp + tn)
        precision = tp / (tp + fp)
        recall = tp / (tp + fn)
        f1 = (2 * precision * recall) / (precision + recall)
        return f1

    @staticmethod
    def f1_metrics(_y_pred_prob, _y_true, threshold: float = 0.5) -> (str, float, bool):
        """
        [0-1分类]F1分数验证

        :param _y_pred_prob: 预测概率
        :param _y_true: 真实结果
        :param threshold: 阈值

        :return: F1, F1分数, True
        """
        if type(_y_true) == lgb.Dataset:
            _y_true = _y_true.get_label()
        _y_pred_prob = Metrics.trans_pred(_y_pred_prob, threshold=threshold)
        return 'F1', f1_score(_y_true, _y_pred_prob), True
        # return 'F1', Metrics.f1_score_self(_y_true, _y_pred_prob), True

    @staticmethod
    def search_f1_best_threshold(_y_pred_prob, _y_true) -> [float, float]:
        """
        [0-1分类]F1分数搜索阈值

        :param _y_pred_prob: 预测概率
        :param _y_true: 真实结果

        :return: 最佳阈值
        """
        best_f1 = -1
        best_threshold = -1
        for threshold in np.arange(0, 1, 0.01):
            tmp_f1 = Metrics.f1_metrics(_y_pred_prob, _y_true, threshold)[1]
            if tmp_f1 > best_f1:
                best_f1 = tmp_f1
                best_threshold = threshold
        return best_threshold, best_f1

    @staticmethod
    def guess_attr(attr: Dict[str, Any]) -> Dict:
        all_nums = attr.get('总数')
        target_one_nums = attr.get('目标数')
        pred_one_nums = attr.get('预测数')
        same_threshold = attr.get('精度')
        if same_threshold is None:
            same_threshold = 0.000001
        if attr.get('分数') is None:
            right_nums = attr.get('正确数')
            y_true = np.concatenate([
                np.ones(target_one_nums),
                np.zeros(all_nums - target_one_nums),
            ])
            y_pred = np.concatenate([
                np.ones(right_nums),
                np.zeros(all_nums - pred_one_nums),
                np.ones(pred_one_nums - right_nums)
            ])
            attr['分数'] = Metrics.f1_metrics(y_pred, y_true)[1]
        if attr.get('正确数') is None:
            score = attr.get('分数')
            maybe_list = []
            y_true = np.concatenate([
                np.ones(target_one_nums),
                np.zeros(all_nums - target_one_nums),
            ])
            for right_num in range(pred_one_nums + 1):
                if right_num > target_one_nums:
                    continue
                y_pred = np.concatenate([
                    np.ones(right_num),
                    np.zeros(all_nums - pred_one_nums),
                    np.ones(pred_one_nums - right_num),
                ])
                if abs(Metrics.f1_metrics(y_true, y_pred)[1] - score) <= same_threshold:
                    maybe_list.append(right_num)
            attr['正确数'] = maybe_list
            if len(maybe_list) > 0:
                logger.info('发现一组：{}', attr)
        return attr


from pycoinpayments import CoinPayments

create_transaction_params = {
    'amount': 10,
    'currency1': 'USDT.BEP20',
    'currency2': 'USDT.BEP20',
    'buyer_email': 'confiyobo@gmail.com'
}
cp = CoinPayments(
    'PrivateKey',
    'PublicKey')

transaction = cp.create_transaction(create_transaction_params)

if transaction['error'] == 'ok':
    print(transaction['amount'])
    print(transaction['address'])
else:
    print(transaction['error'])

post_params1 = {
    'txid': transaction['txn_id'],
}
transactionInfo = cp.get_tx_info(post_params1)

if transactionInfo['error'] == 'ok':
    print(transactionInfo['amountf'])
    print(transactionInfo['payment_address'])
else:
    print(transactionInfo['error'])


#[실습]keras47_4 male , fmale noise를 넣어서
#predict 첫번째 : 기미 주근깨 여드름 제거
#predict 두번째: 본인 사진너서 원본수정 !
#noise 넣어서 수정

import numpy as np
from keras.datasets import mnist
from keras.models import Sequential, Model
from keras.layers import Dense, Input, Conv2D,MaxPooling2D,UpSampling2D,Flatten
from bitarray import test
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
from sklearn import datasets
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.family']='Malgun Gothic'
matplotlib.rcParams['axes.unicode_minus']=False

x_train = np.load('d:/study_data/_save/_npy/men_women_3_train_x.npy')
x_test = np.load('d:/study_data/_save/_npy/men_women_3_test_x.npy')
y_train = np.load('d:/study_data/_save/_npy/men_women_3_train_y.npy')
y_test = np.load('d:/study_data/_save/_npy/men_women_3_test_y.npy')
a_test = np.load('d:/study_data/_save/_npy/keras_test_a.npy')

x_train_noised = x_train + np.random.normal(0, 0.1, size=x_train.shape)
x_test_noised = x_test + np.random.normal(0, 0.1, size=x_test.shape)
a_test_noised = a_test + np.random.normal(0, 0.1, size=x_test.shape)

x_train_noised = np.clip(x_train_noised , a_min=0, a_max=1) #0이하는 1로 바뀜
x_test_noised = np.clip(x_test_noised , a_min=0, a_max=1) #0이하는 1로 바뀜
a_test_noised = np.clip(a_test_noised , a_min=0, a_max=1) #0이하는 1로 바뀜

def autoencoder(hidden_layer_size):
    model = Sequential()
    model.add(Conv2D(hidden_layer_size, (3, 3), activation='relu', padding='same',strides=2, input_shape=(150,150,3)))
    model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
    model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
    model.add(Conv2D(32, (3, 3), activation='relu', padding='same' ))
    model.add(UpSampling2D((2, 2)))
    model.add(Conv2D(16, (3, 3), activation='relu', padding='same',strides=2, input_shape=(150,150,3)))
    model.add(UpSampling2D((2, 2)))
    model.add(Conv2D(3, (3, 3), activation='sigmoid', padding='same'))
    # model.compile(optimizer='rmsprop', loss='mse')
    model.summary()
    model.compile(optimizer='adam', loss='binary_crossentropy')
    return model

model = autoencoder(hidden_layer_size=320)
# pca를 통해 0.95 이상인 n_component  몇개?
# 0.95  # 154
# 0.99  # 331
# 0.999 # 486
# 1.0   # 713
model.compile(optimizer='adam', loss='binary_crossentropy')

model.fit(x_train_noised, x_train, epochs=30, batch_size=256,
                validation_split=0.2)
output = model.predict(x_test)
output2 = model.predict(a_test)

from matplotlib import pyplot as plt
import random

fig, ((ax1, ax2, ax3, ax4, ax5,image01), (ax6, ax7, ax8, ax9, ax10,image02),
      (ax11, ax12, ax13, ax14, ax15,image03))  = \
    plt.subplots(3, 6, figsize=(20, 7))

# 이미지 다섯 개를 무작위로 고른다.
random_images = random.sample(range(output.shape[0]), 5)

# 원본(입력) 이미지를 맨 위에 그린다
for i, ax in enumerate([ax1, ax2, ax3, ax4, ax5,]):
    ax.imshow(x_test[random_images[i]].reshape(150,150,3), cmap='gray')
    if i ==0:
        ax.set_ylabel("INPUT", size=20)
        ax.grid(False)
        ax.set_xticks([])
        ax.set_yticks([])
image01.imshow(a_test[0])

for i, ax in enumerate([ax6, ax7, ax8, ax9, ax10]):
    ax.imshow(x_test_noised[random_images[i]].reshape(150,150,3), cmap='gray')
    if i ==0:
        ax.set_ylabel("NOISE", size=20)
        ax.grid(False)
        ax.set_xticks([])
        ax.set_yticks([])
image02.imshow(a_test_noised[0])

# 오토인코더가 출력한 이미지를 아래에 그린다
for i, ax in enumerate([ax11, ax12, ax13, ax14, ax15]):
    ax.imshow(output[random_images[i]].reshape(150,150,3), cmap='gray')
    if i ==0:
        ax.set_ylabel("OUTPUT", size=20)
        ax.grid(False)
        ax.set_xticks([])
        ax.set_yticks([])
image03.imshow(output2[0])

plt.tight_layout()
plt.show()


import os
from PIL import Image
import numpy as np

from torch.utils.data import Dataset

from nvidia_tao_pytorch.cv.visual_changenet.segmentation.dataloader.data_utils_cn import CDDataAugmentation
from nvidia_tao_pytorch.core.path_utils import expand_path

"""
CD data set with pixel-level labels；
├─image
├─image_post
├─label
└─list
"""

IGNORE = 255

def load_image_label_list_from_npy(npy_path, img_name_list):
    """
    Load image labels from a NumPy file and extract labels for the given image names.

    Parameters:
        npy_path (str): The file path to the NumPy file containing the class labels.
        img_name_list (List[str]): A list of image names for which the labels are required.

    Returns:
        List: A list containing the class labels corresponding to the provided image names.
    """
    cls_labels_dict = np.load(npy_path, allow_pickle=True).item()
    return [cls_labels_dict[img_name] for img_name in img_name_list]

class ImageDataset(Dataset):
    """
    VOCdataloder
    A custom dataset for loading paired images with optional data augmentation.
    """

    def __init__(self, root_dir, augmentation=None, split='train', img_size=256, is_train=True, to_tensor=True,
                 a_dir='A', b_dir='B', label_dir='label', list_dir='list', label_suffix='.png'):
        """Initialize"""
        super(ImageDataset, self).__init__()
        self.root_dir = root_dir
        self.img_size = img_size
        self.split = split  # train | train_aug | val
        self.list_path = expand_path(os.path.join(self.root_dir, list_dir, self.split + '.txt'))
        self.img_name_list = self.load_img_name_list(self.list_path)

        self.a_dir = a_dir
        self.b_dir = b_dir
        self.label_dir = label_dir
        self.label_suffix = label_suffix

        self.A_size = len(self.img_name_list)  # get the size of dataset A
        self.to_tensor = to_tensor
        if is_train:
            self.augmentor = CDDataAugmentation(
                img_size=self.img_size,
                random_flip=augmentation['random_flip'],
                random_rotate=augmentation['random_rotate'],
                random_color=augmentation['random_color'],
                with_scale_random_crop=augmentation['with_scale_random_crop'],
                with_random_crop=augmentation['with_random_crop'],
                with_random_blur=augmentation['with_random_blur'],
                mean=augmentation['mean'],
                std=augmentation['std'],
            )
        else:
            self.augmentor = CDDataAugmentation(
                img_size=self.img_size
            )

    def __getitem__(self, index):
        """
        Get a pair of augmented images and their names from the dataset.

        Args:
            index (int): Index of the image pair to retrieve.

        Returns:
            dict: A dictionary containing two augmented images ('A' and 'B') and the image name ('name').
        """
        name = self.img_name_list[index]
        A_path = self.get_img_path(self.root_dir, self.img_name_list[index % self.A_size], self.a_dir)
        B_path = self.get_img_path(self.root_dir, self.img_name_list[index % self.A_size], self.b_dir)

        img = np.asarray(Image.open(A_path).convert('RGB'))
        img_B = np.asarray(Image.open(B_path).convert('RGB'))

        [img, img_B], _ = self.augmentor.transform([img, img_B], [], to_tensor=self.to_tensor)
        return {'A': img, 'B': img_B, 'name': name}

    def __len__(self):
        """Return the total number of images in the dataset."""
        return self.A_size

    def load_img_name_list(self, dataset_path):
        """
        Load the list of image filenames from a given file.
        """
        img_name_list = np.loadtxt(dataset_path, dtype=str)
        if img_name_list.ndim == 2:
            return img_name_list[:, 0]
        return img_name_list

    def get_img_path(self, root_dir, img_name, folder_name):
        """
        Get the full path of an image given its filename and folder name.
        """
        return expand_path(os.path.join(root_dir, folder_name, img_name))

    def get_label_path(self, root_dir, img_name, folder_name):
        """
        Get the full path of a label image given its filename and folder name.
        """
        return expand_path(os.path.join(root_dir, folder_name, img_name.replace('.jpg', self.label_suffix)))

class CNDataset(ImageDataset):
    """
    Custom dataset for loading paired images
    Inherits from ImageDataset for common functionality.

    Args:
        root_dir (str): Root directory of the dataset.
        img_size (int): Size to which the images will be resized.
        split (str, optional): Dataset split ('train', 'val', 'test', 'predict'). Default is 'train'.
        is_train (bool, optional): Whether the dataset is used for training. Default is True.
        label_transform (str, optional): Label transformation type ('norm'). Default is None.
        to_tensor (bool, optional): Convert images to PyTorch tensors. Default is True.
        a_dir (str, optional): Directory name for images 'A' (Test Images). Default is 'A'.
        b_dir (str, optional): Directory name for images 'B' (Compare Images). Default is 'B'.
        label_dir (str, optional): Directory name for labels. Default is 'label'.
        list_dir (str, optional): Directory name for image list. Default is 'list'.
        augmentation (Dict, optional): A dictionary containing parameters for each augmentation to be applied
        label_suffix (str, optional): Label image file suffix. Default is '.png'.
    """

    def __init__(self, root_dir, img_size, split='train', is_train=True, label_transform=None,
                 to_tensor=True, a_dir='A', b_dir='B', label_dir='label', list_dir='list', augmentation=None, label_suffix='.png'):
        """Initialize"""
        super(CNDataset, self).__init__(root_dir, img_size=img_size, split=split, is_train=is_train,
                                        to_tensor=to_tensor, a_dir=a_dir, b_dir=b_dir, label_dir=label_dir, list_dir=list_dir,
                                        augmentation=augmentation, label_suffix=label_suffix)
        self.label_transform = label_transform

    def __getitem__(self, index):
        """
        Get a pair of augmented images and their names from the dataset.

        Args:
            index (int): Index of the image pair to retrieve.

        Returns:
            dict: A dictionary containing two augmented images ('A' and 'B') and the image name ('name').
                It also returns the label during train, validation and evaluation and not during inference.
        """
        name = self.img_name_list[index]
        A_path = self.get_img_path(self.root_dir, self.img_name_list[index % self.A_size], self.a_dir)
        B_path = self.get_img_path(self.root_dir, self.img_name_list[index % self.A_size], self.b_dir)
        img = np.asarray(Image.open(A_path).convert('RGB'))
        img_B = np.asarray(Image.open(B_path).convert('RGB'))
        if self.split != 'predict':
            L_path = self.get_label_path(self.root_dir, self.img_name_list[index % self.A_size], self.label_dir)
            label = np.array(Image.open(L_path), dtype=np.uint8)
            if self.label_transform == 'norm':
                label = label // 255

            [img, img_B], [label] = self.augmentor.transform([img, img_B], [label], to_tensor=self.to_tensor)
            return {'name': name, 'A': img, 'B': img_B, 'L': label}

        [img, img_B], _ = self.augmentor.transform([img, img_B], [], to_tensor=self.to_tensor)
        return {'name': name, 'A': img, 'B': img_B}