import sys
from JackTokenizer import JackTokenizer

# for compiling expressions
op = ["+", "-", "*", "/", "&", "|", "<", ">", "="]
unaryOp = ["-", "~"]
KeyWordConstant = ["true", "false", "null", "this"]

class CompilationEngine(object):
    """This class recursively compiles a .jack file into (eventually) vm code.
    For now, this just outputs a grammar xml file"""

    def __init__(self, inFile):
        super(CompilationEngine, self).__init__()
        # create an internal tokenizer to iterate through
        self.tokenizer = JackTokenizer(inFile)

        # spacing so I can make nicely formatted xml, this will increase by
        # 4 spaces every time I recurse
        self.spacing = ""

        # setup the output file
        self.outputPath = inFile.name.replace(".jack", ".xml")
        self.outputFile = open(self.outputPath, 'w')
        self.outputFile.close()
        self.outputFile = open(self.outputPath, 'a')

    def __increaseSpacing(self):
        """Adds 2 spaces to self.spacing"""
        self.spacing += "  "

    def __decreaseSpacing(self):
        """Removes 2 spaces from self.spacing"""
        self.spacing = self.spacing[:-2]

    def __writeFullTag(self, tag, value):
        """Writes the spacing, then <tag> value </tag> to the output file"""
        self.outputFile.write(self.spacing + "<" + tag +
                              "> " + value + " </" + tag + ">\n")

    def __writeOpenTag(self, tag):
        """Writes spacing, then <tag>, then increases the spacing"""
        self.outputFile.write(self.spacing + "<" + tag + ">\n")
        self.__increaseSpacing()

    def __writeCloseTag(self, tag):
        """Decreases spacing, then writes spacing, then </tag>"""
        self.__decreaseSpacing()
        self.outputFile.write(self.spacing + "</" + tag + ">\n")

    def start(self):
        """Starts the compilation by creating the token XML file
        and then calling __compileClass()"""

        # start the tokenizer
        self.tokenizer.advance()

        # make token xml file
        self.__createTokenXML()

        # reset tokenizer and compile
        self.tokenizer.reset()
        self.tokenizer.advance()
        self.__compileClass()

    def __createTokenXML(self):
        """Creates the token XML file for a .jack file"""
        outputPath = self.outputPath.replace(".xml", "T.xml")
        f = open(outputPath, 'w')
        f.close()
        f = open(outputPath, 'a')
        f.write("<tokens>\n")
        # make an output file that is filename but with testXML.xml at end
        while self.tokenizer.hasMoreTokens():
            # output to xml to check
            tokenType = self.tokenizer.tokenType()
            if tokenType == "KEYWORD":
                f.write("<keyword>" + self.tokenizer.keyWord() + "</keyword>\n")
            elif tokenType == "SYMBOL":
                symbol = self.tokenizer.symbol()
                symbol = symbol.replace("&", "&")
                symbol = symbol.replace("<", "<")
                symbol = symbol.replace(">", ">")
                symbol = symbol.replace("\"", """)
                f.write("<symbol>" + symbol + "</symbol>\n")
            elif tokenType == "IDENTIFIER":
                f.write("<identifier>" +
                        self.tokenizer.identifier() + "</identifier>\n")
            elif tokenType == "INT_CONST":
                f.write("<integerConstant>" +
                        self.tokenizer.intVal() + "</integerConstant>\n")
            elif tokenType == "STRING_CONST":
                f.write("<stringConstant>" +
                        self.tokenizer.stringVal() + "</stringConstant>\n")

            self.tokenizer.advance()

        # close the xml tag
        f.write("</tokens>")

    def __compileType(self):
        """Compiles a complete jack type grammar. Returns false if there is an error"""
        # check for valid keyword
        if self.tokenizer.tokenType() == "KEYWORD":
            k = self.tokenizer.keyWord()
            if k not in ["int", "char", "boolean"]:
                print("Error: type keyword must be int, char, or boolean")
                return False
            self.__writeFullTag("keyword", k)
            self.tokenizer.advance()
            return True
        # check for className
        else:
            res = self.__compileClassName()
            # if __compileClassName() errors, this is not a valid type
            if not res:
                print("Error: type not a valid className")
            return res

    def __compileClassName(self):
        """Compiles a complete jack className grammar. Returns false if there is
        an error"""
        if self.tokenizer.tokenType() != "IDENTIFIER":
            return False
        self.__writeFullTag("identifier", self.tokenizer.identifier())
        self.tokenizer.advance()
        return True

    def __compileSubroutineName(self):
        """Compiles a complete jack subroutineName. Returns false if there is an
        error"""
        if self.tokenizer.tokenType() != "IDENTIFIER":
            return False
        self.__writeFullTag("identifier", self.tokenizer.identifier())
        self.tokenizer.advance()
        return True

    def __compileVarName(self):
        """Compiles a complete jack varName. Returns false if there is an
        error"""
        if self.tokenizer.tokenType() != "IDENTIFIER":
            return False
        self.__writeFullTag("identifier", self.tokenizer.identifier())
        self.tokenizer.advance()
        return True

    def __compileClass(self):
        """Compiles a complete jack class grammar"""
        # find the class keyword
        if self.tokenizer.tokenType() != "KEYWORD" or \
                self.tokenizer.keyWord() != "class":
            print("Error: no class declaration found")
            sys.exit(1)
        # write both the class tag and the keyword tag for class
        self.__writeOpenTag("class")
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # find the className
        if not self.__compileClassName():
            print("Error: no class name found in class declaration")
            sys.exit(1)

        # find the open curly brace
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "{":
            print("Error: no opening brace found after class")
            sys.exit(0)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # compile the classVarDecs
        while(self.tokenizer.tokenType() == "KEYWORD" and
              (self.tokenizer.keyWord() == "static" or
               self.tokenizer.keyWord() == "field")):
            self.__compileClassVarDec()

        # compile the subroutines
        while(self.tokenizer.tokenType() == "KEYWORD" and
              (self.tokenizer.keyWord() == "constructor" or
               self.tokenizer.keyWord() == "function" or
               self.tokenizer.keyWord() == "method")):
            self.__compileSubroutineDec()

        # find last curly brace
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "}":
            print("Error: no closing brace found after class definition")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # close class tag
        self.__writeCloseTag("class")
        self.tokenizer.advance()

    def __compileClassVarDec(self):
        """Compiles a complete jack class variable declaration. This advances the
        tokenizer completely through the variable declaration"""
        # since we already checked to make sure this is valid, we can write
        # the tag here and either static or filed
        self.__writeOpenTag("classVarDec")
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # look for a valid type
        if not self.__compileType():
            sys.exit(1)

        # check for varName
        if not self.__compileVarName():
            print("Error: missing varName identifier in classVarDec")
            sys.exit(1)

        # check for comma then more varNames (possible not existing)
        while self.tokenizer.tokenType() == "SYMBOL" and \
                self.tokenizer.symbol() == ",":
            # write the comma
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

            # check for varName again
            if not self.__compileVarName():
                print("Error: missing varName identifier in classVarDec")
                sys.exit(1)

        # check for closing semicolon
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ";":
            print("Error: missing semicolon after classVarDec")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # close classVarDec tag
        self.__writeCloseTag("classVarDec")

    def __compileSubroutineDec(self):
        """Compiles a complete jack subroutine description. This advances the
        tokenizer completely through the subroutine declaration"""
        # write the opening tag
        self.__writeOpenTag("subroutineDec")
        # since we already checked for constructor/function/method, write it
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # look for void or type
        if self.tokenizer.tokenType() == "KEYWORD" and \
                self.tokenizer.keyWord() == "void":
            # if void, write it
            self.__writeFullTag("keyword", self.tokenizer.keyWord())
            self.tokenizer.advance()
        elif not self.__compileType():
            print("Error: subroutine return type not void or valid type")
            sys.exit(1)

        # check for subroutineName
        if not self.__compileSubroutineName():
            print("Error: missing subroutineName in subroutineDec")
            sys.exit(1)

        # check for open parentheses
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "(":
            print("Error: missing ( for parameter list")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # do parameter list (this could add nothing)
        self.__compileParameterList()

        # check for closing parentheses
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ")":
            print("Error: missing ) for parameter list")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # compile subroutine body
        self.__compileSubroutineBody()

        # close subroutineDec tag
        self.__writeCloseTag("subroutineDec")

    def __compileParameterList(self):
        """Compiles a complete jack parameter list grammar"""
        # write opening tag
        self.__writeOpenTag("parameterList")

        # if the next symbol is a ), then there is no parameter list, so just return
        # the rest of compileSubroutine will handle writing that
        if self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == ")":
            # close tag
            self.__writeCloseTag("parameterList")
            return
        # look for a valid type
        else:
            res = self.__compileType()
            if not res:
                sys.exit(1)

            # check for varName
            if not self.__compileVarName():
                print("Error: missing varName identifier in parameterList")
                sys.exit(1)

            # check for comma separated list of type and varName
            while self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == ",":
                # write the comma
                self.__writeFullTag("symbol", self.tokenizer.symbol())
                self.tokenizer.advance()

                # look for a valid type
                if not self.__compileType():
                    sys.exit(1)

                # check for varName
                if not self.__compileVarName():
                    print("Error: missing varName identifier in parameterList")
                    sys.exit(1)

            # write closing tag
            self.__writeCloseTag("parameterList")

    def __compileSubroutineBody(self):
        """Compile a complete jack subroutine body grammar"""
        # write opening tag
        self.__writeOpenTag("subroutineBody")

        # check for {
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "{":
            print("Error: missing { for subroutine body")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # check to see if we need to compile varDec
        while self.tokenizer.tokenType() == "KEYWORD" and \
                self.tokenizer.keyWord() == "var":
            self.__compileVarDec()

        # compile statements
        self.__compileStatements()

        # check for closing }
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "}":
            print("Error: missing closing } for subroutine body")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # close tag
        self.__writeCloseTag("subroutineBody")
        return

    def __compileVarDec(self):
        """Compiles a complete jack varDec grammar"""
        # write open tag
        self.__writeOpenTag("varDec")
        # since we already checked to make sure there is a var, write it
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # check for type
        if not self.__compileType():
            sys.exit(1)

        # check for varName
        if not self.__compileVarName():
            print("Error: missing varName identifier in varDec")
            sys.exit(1)

        # check for comma separated list of type and varName
        while self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == ",":
            # write the comma
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

            # check for varName
            if not self.__compileVarName():
                print("Error: missing varName identifier in varDec")
                sys.exit(1)

        # check for semicolon
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ";":
            print("Error: missing ; after varDec")
            sys.exit(1)
        # write ;
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # close tag
        self.__writeCloseTag("varDec")

        return

    def __compileStatements(self):
        """Compiles a complete jack statements grammar"""
        # write statements tag
        self.__writeOpenTag("statements")

        # check for the keywords for all the statements
        while self.tokenizer.tokenType() == "KEYWORD":
            k = self.tokenizer.keyWord()

            if k == "let":
                self.__compileLetStatement()
            elif k == "if":
                self.__compileIfStatement()
            elif k == "while":
                self.__compileWhileStatement()
            elif k == "do":
                self.__compileDoStatement()
            elif k == "return":
                self.__compileReturnStatement()
            else:
                print("Error: invalid statment " + k)
                sys.exit(1)

        # close statements tag
        self.__writeCloseTag("statements")

    def __compileLetStatement(self):
        """Compiles a complete jack let statment grammar"""
        # write opening tag
        self.__writeOpenTag("letStatement")
        # since we already checked for the keyword let, write it
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # look for varName
        if not self.__compileVarName():
            print("Error: missing varName for let statement")

        # check for [
        if self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == "[":
            # write the bracket
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

            # compile expression
            self.__compileExpression()

            # write the closing bracket
            if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "]":
                print("Error: missing closing ] in let statement")
                sys.exit(1)
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

        # check for =
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "=":
            print("Error: missing = in let expression")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # compile expression
        self.__compileExpression()

        # look for ;
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ";":
            print("Error: missing ; after let statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # write closing tag
        self.__writeCloseTag("letStatement")

    def __compileIfStatement(self):
        """Compiles a complete jack if statement grammar"""
        # write opening tag
        self.__writeOpenTag("ifStatement")
        # since we already checked for if, write it
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # check for (
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "(":
            print("Error: missing ( in if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # compile expression
        self.__compileExpression()

        # check for )
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ")":
            print("Error: missing ) in if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # check for {
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "{":
            print("Error: missing { for if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # compile more statements
        self.__compileStatements()

        # check for }
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "}":
            print("Error: missing } after if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # check for else
        if self.tokenizer.tokenType() == "KEYWORD" and self.tokenizer.keyWord() == "else":
            # write else
            self.__writeFullTag("keyword", self.tokenizer.keyWord())
            self.tokenizer.advance()

            # check for {
            if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "{":
                print("Error: missing { for if statement")
                sys.exit(1)
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

            # compile more statements
            self.__compileStatements()

            # check for }
            if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "}":
                print("Error: missing } after if statement")
                sys.exit(1)
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

        # close tag
        self.__writeCloseTag("ifStatement")

    def __compileWhileStatement(self):
        """Compiles a complete jack while statement grammar"""
        # write opening tag
        self.__writeOpenTag("whileStatement")
        # since we checked for while already, write it
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # check for (
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "(":
            print("Error: missing ( in if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # compile expression
        self.__compileExpression()

        # check for )
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ")":
            print("Error: missing ) in if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # check for {
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "{":
            print("Error: missing { for if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # compile more statements
        self.__compileStatements()

        # check for }
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "}":
            print("Error: missing } after if statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # write closing tag
        self.__writeCloseTag("whileStatement")

    def __compileDoStatement(self):
        """Compiles a complete jack do statement grammar"""
        # write opening tag
        self.__writeOpenTag("doStatement")
        # since we already checked for do, write it
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # compile subroutine call
        self.__compileSubroutineCall()

        # check for semicolon
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ";":
            print("Error: missing ; after do statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # write closing tag
        self.__writeCloseTag("doStatement")

    def __compileReturnStatement(self):
        """Compiles a complete jack return statement grammar"""
        # write opening tag
        self.__writeOpenTag("returnStatement")
        # since we checked for return already, write it
        self.__writeFullTag("keyword", self.tokenizer.keyWord())
        self.tokenizer.advance()

        # if the next symbol isn't a symbol, it must be an expression
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ";":
            self.__compileExpression()

        # write ;, checking again to make sure after calling compile expression
        # that the next symbol is still a valid ;
        if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ";":
            print("Error: missing ; after return statement")
            sys.exit(1)
        self.__writeFullTag("symbol", self.tokenizer.symbol())
        self.tokenizer.advance()

        # write closing tag
        self.__writeCloseTag("returnStatement")

    def __convertOp(self, op):
        """Converts the operators that interfere with xml tags to their properly
        escaped versions"""
        op = op.replace("&", "&")
        op = op.replace("<", "<")
        op = op.replace(">", ">")
        op = op.replace("\"", """)

        return op

    def __compileExpression(self):
        """Compiles a complete jack expression grammar"""
        # write opening tag
        self.__writeOpenTag("expression")

        # compile term
        self.__compileTerm()

        # check for op
        while self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() in op:
            s = self.tokenizer.symbol()

            # write op
            self.__writeFullTag("symbol", self.__convertOp(s))
            self.tokenizer.advance()

            # compile another term
            self.__compileTerm()

        # close tag
        self.__writeCloseTag("expression")

    def __compileTerm(self):
        """Compiles a complete jack term grammar"""
        # write opening tag
        self.__writeOpenTag("term")

        # term logic
        # check for integerConstant
        if self.tokenizer.tokenType() == "INT_CONST":
            self.__writeFullTag("integerConstant", self.tokenizer.intVal())
            self.tokenizer.advance()
        # check for string constant
        elif self.tokenizer.tokenType() == "STRING_CONST":
            self.__writeFullTag("stringConstant", self.tokenizer.stringVal())
            self.tokenizer.advance()
        # check for keyword for KeywordConstant
        elif self.tokenizer.tokenType() == "KEYWORD":
            k = self.tokenizer.keyWord()

            if k not in KeyWordConstant:
                print("Error: invalid KeyWordConstant" + k + " in term")
                sys.exit(1)

            # write the keywordconstant
            self.__writeFullTag("keyword", k)
            self.tokenizer.advance()
        # check for symbol for either ( expression ) or unary op
        elif self.tokenizer.tokenType() == "SYMBOL":
            s = self.tokenizer.symbol()

            # ( expression )
            if s == "(":
                self.__writeFullTag("symbol", s)
                self.tokenizer.advance()

                # compile expression
                self.__compileExpression()

                # check for )
                if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ")":
                    print("Error: missing ) after expression in term")
                    sys.exit(1)
                self.__writeFullTag("symbol", self.tokenizer.symbol())
                self.tokenizer.advance()
            # unaryOp term
            elif s in unaryOp:
                self.__writeFullTag("symbol", s)
                self.tokenizer.advance()

                # compile term
                self.__compileTerm()
            else:
                print("Error: invalid symbol " + s + " in term")
                sys.exit(1)
        # check for varName | varName [ expression ] | subroutineCall
        elif self.tokenizer.tokenType() == "IDENTIFIER":
            # advance the tokenizer one more step to check for [, (, or other
            self.tokenizer.advance()

            if self.tokenizer.tokenType() == "SYMBOL":
                s = self.tokenizer.symbol()

                # varName[expression]
                if s == "[":
                    # go back to varName
                    self.tokenizer.retreat()

                    # compile varName
                    if not self.__compileVarName():
                        print("Error: invalid varName in term")
                        sys.exit(1)

                    # write [
                    self.__writeFullTag("symbol", self.tokenizer.symbol())
                    self.tokenizer.advance()

                    # compile expression
                    self.__compileExpression()

                    # write ]
                    if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != "]":
                        print("Error: missing ] after varName[expression]")
                        sys.exit(1)
                    self.__writeFullTag("symbol", self.tokenizer.symbol())
                    self.tokenizer.advance()
                # subroutineCall
                elif s == "(" or s == ".":
                    # go back to subroutineName
                    self.tokenizer.retreat()

                    # compile subroutineCall
                    self.__compileSubroutineCall()
                else:
                    # go back to varName
                    self.tokenizer.retreat()

                    # compile varName
                    if not self.__compileVarName():
                        print("Error: invalid varName in term")
                        sys.exit(1)
            else:
                # go back to varName
                self.tokenizer.retreat()

                # compile varName
                if not self.__compileVarName():
                    print("Error: invalid varName in term")
                    sys.exit(1)
        else:
            print("Error: invalid term")
            sys.exit(1)

        # close tag
        self.__writeCloseTag("term")

    def __compileSubroutineCall(self):
        """Compiles a complete jack subroutine call grammar"""
        # look ahead one token to see if it is a ( or a .
        self.tokenizer.advance()

        # subroutineName
        if self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == "(":
            # go back to subroutine name
            self.tokenizer.retreat()

            # compile subroutine name
            if not self.__compileSubroutineName():
                print("Error: invalid subroutineName in subroutineCall")
                sys.exit(1)

            # check for (
            if self.tokenizer.tokenType() != "SYMBOL" and self.tokenizer.symbol() != "(":
                print("Error: missing ( in subroutineCall before expressionList")
                sys.exit(1)
            # write (
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

            # compile expression list
            self.__compileExpressionList()

            # check for )
            if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ")":
                print("Error: missing ) after expressionList in subroutineCall")
                sys.exit(1)
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()
        # className | varName
        elif self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == ".":
            # go back to varName/className
            self.tokenizer.retreat()

            if self.tokenizer.tokenType() != "IDENTIFIER":
                print("Error: missing varName|className in subroutineCall")
            # Hacky, but className and varName both correspond to just an
            # identitifer, so I just call compileVarName to handle both
            if not self.__compileVarName():
                print("Error: invalid className or varName in subroutineCall")
                sys.exit(1)

            # check for .
            if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ".":
                print("Error: missing . in subroutineCall")
                sys.exit(1)
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

            # compile subroutineName
            if not self.__compileSubroutineName():
                print("Error: missing subroutineName after . in subroutineCall")
                sys.exit(1)

            # check for (
            if self.tokenizer.tokenType() != "SYMBOL" and self.tokenizer.symbol() != "(":
                print("Error: missing ( in subroutineCall before expressionList")
                sys.exit(1)
            # write (
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()

            # compile expression list
            self.__compileExpressionList()

            # check for )
            if self.tokenizer.tokenType() != "SYMBOL" or self.tokenizer.symbol() != ")":
                print("Error: missing ) after expressionList in subroutineCall")
                sys.exit(1)
            self.__writeFullTag("symbol", self.tokenizer.symbol())
            self.tokenizer.advance()
        else:
            print("Error: invalid subroutineCall")
            sys.exit(1)

    def __compileExpressionList(self):
        """Compiles a complete jack expression list grammar"""
        # write open tag
        self.__writeOpenTag("expressionList")

        # if the symbol is ), there is no expression list
        if self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == ")":
            # close tag
            self.__writeCloseTag("expressionList")
            return
        else:
            # compile expression
            self.__compileExpression()

            # loop until you dont see a comma
            while self.tokenizer.tokenType() == "SYMBOL" and self.tokenizer.symbol() == ",":
                # write ,
                self.__writeFullTag("symbol", self.tokenizer.symbol())
                self.tokenizer.advance()

                # compile expression
                self.__compileExpression()

            # write closing tag
            self.__writeCloseTag("expressionList")


from enum import Enum
from typing import Any, Type, Union

import pytest
from databento.common.enums import Encoding
from databento.common.validation import (
    validate_enum,
    validate_gateway,
    validate_maybe_enum,
    validate_path,
    validate_semantic_string,
    validate_smart_symbol,
)

class TestValidation:
    @pytest.mark.parametrize(
        "value",
        [
            [None, 0],
        ],
    )
    def test_validate_path_given_wrong_types_raises_type_error(
        self,
        value: Any,
    ) -> None:
        # Arrange, Act, Assert
        with pytest.raises(TypeError):
            validate_path(value, "param")

    @pytest.mark.parametrize(
        "value, enum",
        [
            [None, Encoding],
        ],
    )
    def test_validate_enum_given_wrong_types_raises_type_error(
        self,
        value: Any,
        enum: Type[Enum],
    ) -> None:
        # Arrange, Act, Assert
        with pytest.raises(TypeError):
            validate_enum(value, enum, "param")

    def test_validate_enum_given_invalid_value_raises_value_error(self) -> None:
        # Arrange, Act, Assert
        with pytest.raises(ValueError):
            validate_enum("invalid", Encoding, "encoding")

    @pytest.mark.parametrize(
        "value, enum, expected",
        [
            ["dbn", Encoding, "dbn"],
            ["DBN", Encoding, "dbn"],
            [Encoding.DBN, Encoding, "dbn"],
        ],
    )
    def test_validate_enum_given_valid_value_returns_expected_output(
        self,
        value: Union[str, Enum],
        enum: Type[Enum],
        expected: Union[str, Enum],
    ) -> None:
        # Arrange, Act, Assert
        assert validate_enum(value, enum, "param") == expected

    def test_validate_maybe_enum_give_none_returns_none(self) -> None:
        # Arrange, Act, Assert
        assert validate_maybe_enum(None, Encoding, "encoding") is None

    @pytest.mark.parametrize(
        "url, expected",
        [
            pytest.param("databento.com", "https://databento.com"),
            pytest.param("hist.databento.com", "https://hist.databento.com"),
            pytest.param("http://databento.com", "https://databento.com"),
            pytest.param("http://hist.databento.com", "https://hist.databento.com"),
            pytest.param("//", ValueError),
            pytest.param("", ValueError),
        ],
    )
    def test_validate_gateway(
        self,
        url: str,
        expected: Union[str, Type[Exception]],
    ) -> None:
        """
        Tests several correct and malformed URLs.
        """
        if isinstance(expected, str):
            assert validate_gateway(url) == expected
        else:
            with pytest.raises(expected):
                validate_gateway(url)

    @pytest.mark.parametrize(
        "symbol, expected",
        [
            pytest.param("ES", "ES"),
            pytest.param("es", "ES"),
            pytest.param("ES.FUT", "ES.FUT"),
            pytest.param("es.opt", "ES.OPT"),
            pytest.param("ES.C.0", "ES.c.0"),
            pytest.param("es.c.5", "ES.c.5"),
            pytest.param(".v.2", ValueError),
            pytest.param("es..9", ValueError),
            pytest.param("es.n.", ValueError),
            pytest.param("es.c.5.0", ValueError),
            pytest.param("", ValueError),
        ],
    )
    def test_validate_smart_symbol(
        self,
        symbol: str,
        expected: Union[str, Type[Exception]],
    ) -> None:
        """ """
        if isinstance(expected, str):
            assert validate_smart_symbol(symbol) == expected
        else:
            with pytest.raises(expected):
                validate_smart_symbol(symbol)

@pytest.mark.parametrize(
    "value,expected",
    [
        pytest.param("nick", "nick"),
        pytest.param("", ValueError, id="empty"),
        pytest.param(" ", ValueError, id="whitespace"),
        pytest.param("foo\x00", ValueError, id="unprintable"),
    ],
)
def test_validate_semantic_string(
    value: str,
    expected: Union[str, Type[Exception]],
) -> None:
    """
    Test that validate_semantic_string rejects string which are:
        - empty
        - whitespace
        - contain unprintable characters
    """
    if isinstance(expected, str):
        assert validate_semantic_string(value, "unittest") == expected
    else:
        with pytest.raises(expected):
            assert validate_semantic_string(value, "")


import numpy as np
from matplotlib import ticker
import matplotlib.pyplot as plt
import matplotlib.animation as ani

from simulation.sim_ToolBox import checkState
from simulation.sim_ToolBox import checkState_FH

def update_elevators(s, N_elev, N_floors):
    '''

    Parameters
    ----------
    s : State object
        This is the state object whose predicates are going to be translated
        into a matrix representation.
    N_elev : int
        Number of elevators
    N_floors : int
        Number of floors

    Returns
    -------
    newElevators : dict
        This dictionary stores all the information related to the floor, pax,
        door position and direction for each elevator
    newFloors : np.array N_floors x (N_elev + 1)
        This is a matrix representation of the current position of the people
        and the elevators

    '''
    newFloors = np.zeros( (N_floors, N_elev + 1) )          # init a new matrix

    # The first column represents the people waiting for the elevators

    newElevators = {}
    newElevators['e0'] = {}
    newElevators['e0']['up'] = -1.0
    newElevators['e0']['closed'] = -1.0
    newElevators['e0']['person'] = 0.0

    if N_elev > 1:
        newElevators['e1']={}
        newElevators['e1']['up'] = 0.0
        newElevators['e1']['closed'] = 0.0
        newElevators['e1']['person'] = 0.0

    for pred in s.predicates:            # proccess all the state predicates
        words = pred.split('_')          # split the string

        if words[1]=='dir':
            elev = words[-1]
            newElevators[elev]['up'] = 1.0

        elif words[1]=='closed':
            elev = words[-1]
            newElevators[elev]['closed']= 1.0

        elif words[1] == 'in':
            elev = words[-1]
            if words[4] == 'up':
                newElevators[elev]['person'] = 1.0
            else:
                newElevators[elev]['person'] = -1.0

        elif words[1] == 'waiting':
            floor = int(words[-1][-1])
            index = N_floors -1 - floor
            if words[2] == 'up':
                newFloors[index][0] = 1.0
            else :
                newFloors[index][0] = -1.0

        elif words[1] == 'at':
            floor = int(words[-1][-1])
            index = N_floors -1 - floor
            elev = words[4]
            newElevators[elev]['floor'] = floor
            newFloors[index][int(elev[-1])+1] = 1.0

        else:
            print('Something went wrong')

    return newElevators, newFloors

def sim_Elevators(s0, G, horizon, problem):
    '''

    Parameters
    ----------
    s0 : state object
        This is the initial state of the simulation
    G : dictionary
        This is the graph that contains the plan or policy solution.
    horizon : int
        This is the max. number of decission epochs. In other words, the
        finite horizon of the MDP
    problem : int
        It is simply an identifier to decided the configuration of the problem
        be ware of the fact that this is a hardcoded customisation.

    Returns
    -------
    None.

    This function has the same goal as runSim_FH, in fact, it works with the
    same idea. The difference between this functions is that this function is
    specifically designed for 'elevators mdp' and it is able to find
    a graphic represention of the simulation and save it in a .gif file.

    '''

    # Define the grid size according to the problem
    n_elevators = 0
    n_floors = 0

    if problem == 1:
        n_elevators = 1
        n_floors = 3

    elif problem <= 3:
        n_elevators = 2
        n_floors = 3

    elif problem == 4:
        n_elevators = 1
        n_floors = 4

    elif problem <= 6:
        n_elevators = 2
        n_floors = 4

    elif problem == 7:
        n_elevators = 1
        n_floors = 5

    elif problem <= 9:
        n_elevators = 2
        n_floors = 5

    else:
        n_elevators = 1
        n_floors = 6

    floorsList = []                 # List of matrix depicting the evolution of elevators' positions
    elevatorList = []               # List of dictionaries depicting the evolution of elevators' states

    s = s0                          # assign s0 to the current state
    count = 0                       # init a counter to count decission epochs
    accrualCost = 0                 # int a cost/reward adder
    while (count < horizon):

        print(s)

        # Create the matrix and dictionary that depicts this state
        [currentElev, currentFloors] = update_elevators(s, n_elevators, n_floors)

        # Append them to the lists
        floorsList.append(currentFloors)
        elevatorList.append(currentElev)

        if s in G:                   # look for the best action in the policy solution
            for a in G[s].keys():
                if (a != 'N' and a != 'V'):
                    if (G[s][a]['Q-value'] == G[s]['V']):    # look for the action that satisfies V(s) = Q(s,a)
                        action = a
                        break
        else:                        # The plan is incomplte! Sample a random action and pray
            action = s.SampleAction()

        print(action.name)                         # print selected action
        [successor,cost] = s.SampleChild(action)   # Generate new state
        accrualCost += cost                        # update accrual cost
        successor = checkState_FH(successor,G)        # check if the successor is already in the graph
        s = successor                              # assing successor to the current state
        count +=1                                  # update counter

    print("Terminal State")
    print(accrualCost)

    # The simulation is done -> go on with the graphical representation
    fig = plt.figure()                                # Create an empty figure with no axes
    fig, ax_lst = plt.subplots(1, n_elevators + 1)    # Add a 1x(2-3) grid of Axes
    fig.suptitle('Elevators p' + str(problem))        # Add a title to the figure

    def newFrame(i=int):
        e1_state = [ elevatorList[i]['e0']['up'], elevatorList[i]['e0']['closed'], elevatorList[i]['e0']['person'] ]

        ax_lst[0].matshow(floorsList[i], cmap=plt.cm.Spectral, vmin = -1.0 , vmax = 1.0)
        ax_lst[1].cla()
        ax_lst[1].set_title('e0')
        ax_lst[1].set_ylim(-1,1)
        ax_lst[1].yaxis.set_major_locator(ticker.FixedLocator([-1, 1]))
        ax_lst[1].bar(['Up','Closed','person'], e1_state , color = 'cyan')

        if n_elevators > 1:
            e2_state = [ elevatorList[i]['e1']['up'], elevatorList[i]['e1']['closed'], elevatorList[i]['e1']['person'] ]

            ax_lst[2].cla()
            ax_lst[2].set_title('e1')
            ax_lst[2].set_ylim(-1,1)
            ax_lst[2].yaxis.set_major_locator(ticker.FixedLocator([-1, 1]))
            ax_lst[2].bar(['Up','Closed','person'], e2_state, color = 'cyan')

    animator = ani.FuncAnimation(fig, newFrame, interval = 400)
    animator.save(r'C:\Users\alber\Documents\ISAE-MAE\Research project\MyAlgorithms\learning2parse\output\elevatorsP1_FH50k.gif')

    return


from flask import Flask, render_template
from flask.ext.socketio import SocketIO, emit

import json
import plotly
import numpy as np

import dash.utils as utils
from dash.components import element as el
from dash.components import graph

name = 'dash-1-hello-world'
app = Flask(name)
app.debug = True
app.config['key'] = 'secret'
socketio = SocketIO(app)

# Write the HTML "includes" blocks to /templates/runtime/dash-1-hello-world
# Alternatively, include the HTML yourself in that folder
utils.write_templates(
    {
        'header': [
            el('H1', {}, 'Hello Dash')
        ],

        'controls': [
            el('label', {}, 'Frequency'),
            el('input', {
                'type': 'range',
                'class': 'u-full-width show-values',
                'name': 'frequency',
                'value': 0,
                'min': 1,
                'max': 10,
                'step': 0.1
            }),
            el('label', {}, 'Title'),
            el('input', {
                'type': 'text',
                'name': 'title',
                'placeholder': 'Type away',
                'class': 'u-full-width'
            }, '')
        ],

        'main_pane': [
            graph('sine-wave')
        ]
    }, name
)

@app.route('/')
def index():
    return render_template('layouts/layout_single_column_and_controls.html',
                           app_name=name)

@socketio.on('replot')
def replot(app_state):
    print(app_state)
    frequency = float(app_state['frequency'])
    x = np.linspace(0, 2 * 3.14, 500)
    y = np.sin(frequency * x)
    messages = [
        {
            'id': 'sine-wave',
            'task': 'newPlot',
            'data': [{
                'x': x,
                'y': y,
            }],
            'layout': {
                'xaxis': {
                    'range': [0, 2 * 3.14]
                },
                'title': app_state.get('title', '')
            }
        }
    ]

    emit('postMessage', json.dumps(messages,
                                   cls=plotly.utils.PlotlyJSONEncoder))

if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=9991)


import numpy as np
import getopt, sys
import pdb
import random
import math
import matplotlib.pyplot as plt
import matplotlib.lines as lines

verbose  = False
debug   = False
data = []

def read_data(path):
    data = []
    with open(path) as tsp:
        for line in tsp:
            token = line.split()
            d = {'n': int(token[0]), 'x': float(token[1]), 'y': float(token[2])}
            data.append(d)
    return data

def draw(nodes):
    fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(10, 10))
    axes.set_title('Nearest Neighbor')
    axes.set_xlabel('X')
    axes.set_ylabel('Y')
    for i in range(len(nodes)):
        node = data[nodes[i]]
        axes.scatter(node['x'], node['y'], color='black', s=100)
    for i in range(nodes.__len__() - 1):
        node1 = data[nodes[i]]
        node2 = data[nodes[i + 1]]
        axes.add_line(
            lines.Line2D([node1['x'], node2['x']],
                         [node1['y'], node2['y']]))
        axes.text((node1['x'] + node2['x']) / 2,
                  (node1['y'] + node2['y']) / 2,
                  i + 1, fontsize=15)
    plt.show()

def rank_selection(items, probs, n):
    if n == 0:
        return np.array([])

    arg_sort = np.argsort(probs)
    sorted_items = []
    for index in arg_sort:
        sorted_items.append(items[index])
    N = len(items)
    rank = np.arange(start=1, stop=N + 1)
    prob_new = 2 * rank / (N * (N + 1))

    rnds = np.random.random(size=n)
    inds = np.zeros(n, dtype=np.int)
    cum_sum = np.cumsum(prob_new)
    for i, rnd in enumerate(rnds):
        inds[i] = np.argmax(cum_sum >= rnd)
    return np.array(sorted_items)[inds]

def chromosome_generator(genes_num, chromosome_num):
    chromosomes = []
    for i in range(chromosome_num):
        c = np.random.permutation(194).tolist()
        chromosomes.append(c)
    return chromosomes

def evaluator(chromosome):
    global data
    distance = 0
    for i in range(len(chromosome)):
        num1        = chromosome[i]
        num2        = chromosome[(i + 1) % len(chromosome)]
        a1          = data[num1]
        a2          = data[num2]
        dx          = a1['x'] - a2['x']
        dy          = a1['y'] - a2['y']
        distance    = distance + math.sqrt(dx * dx + dy * dy)
    return distance

def roulette_wheel_selection(items, probs, n):
    rnds = np.random.random(size=n)
    inds = np.zeros(n, dtype=np.int)
    cum_sum = np.cumsum(probs)
    for i, rnd in enumerate(rnds):
        inds[i] = np.argmax(cum_sum >= rnd)
    return items[inds]

def cross_over(parent1, parent2, parameters=None):
    parent1 = parent1
    parent2 = parent2
    n = len(parent1)
    start_substr = random.randint(0, n - 2)
    end_substr = random.randint(start_substr + 1, n - 1)
    child1 = parent1.copy()
    child2 = parent2.copy()
    j = end_substr
    i = end_substr
    while j != start_substr:
        if i == 194:
            print('end', end_substr)
            print('here')
        if not parent1[i] in child2[start_substr:end_substr]:
            child2[j] = parent1[i]
            j = (j + 1) % n
        i = (i + 1) % n
        if i == 194:
            print('here')
    j = end_substr
    i = end_substr
    while j != start_substr:
        if not parent2[i] in child1[start_substr:end_substr]:
            child1[j] = parent2[i]
            j = (j + 1) % n
        i = (i + 1) % n
    return child1, child2

def mutation(chro, parameters={'prob': 0.05}):
    for i in range(len(chro)):
        if np.random.random() <= parameters['prob']:
            swap_idx = np.random.randint(0, len(chro))
            chro[i], chro[swap_idx] = chro[swap_idx], chro[i]
        return chro

def reverse_mutation(chromosome, parameters={'prob': 0.15}):
    if np.random.random() <= parameters['prob']:
        chr_length = len(chromosome)

        # Two random points is selected to change values of interval
        ind_1 = np.random.randint(chr_length - 1)
        ind_2 = np.random.randint(ind_1, chr_length)

        while ind_1 < ind_2:
            chromosome[ind_1], chromosome[ind_2] = chromosome[ind_2], chromosome[ind_1]
            ind_1 += 1
            ind_2 -= 1
    return chromosome

def q_tournament_selection(items, probs, q, n):

    if n == 0:
        return np.array([])

    else:
        items, probs = warning_data_type_check_selection_algorithms(items, probs)
        index = np.arange(len(items))
        np.random.shuffle(index)
        items = items[index]
        probs = probs[index]

        selected_items = []
        len_items = len(items)

        for i in range(n):
            indexes = np.random.choice(np.arange(len_items), q, replace=False)
            selected_items.append(items[indexes[np.argmax(probs[indexes])]])
    return np.array(selected_items)

def generate_child(parents):
    children = []
    random.shuffle(parents)
    for i in range(0, len(parents) - 1, 2):
        chromosome1, chromosome2 = cross_over(parents[i], parents[i + 1])
        chromosome1 = reverse_mutation(chromosome1)
        chromosome2 = reverse_mutation(chromosome2)
        children += [chromosome1.tolist(), chromosome2.tolist()]
    return children

def population_selection(population, probs):
    pairs = zip(probs, population)
    s_pairs = sorted(pairs)
    tuples = zip(*s_pairs)
    probs, population = [list(t) for t in tuples]
    return np.asarray(population[10:30]), np.matrix(probs[10:30]).T

def main():
    global verbose, debug, data
    opts, args = getopt.getopt(sys.argv[1:], "vd")
    for o, a in opts:
        if o == "-v":
            verbose = True
        elif o == "-d":
            debug = True
    data = read_data('tsp_data.txt')
    if verbose:
        print('data is')
        print(data)
        print("================")
        input()
    population = chromosome_generator(194, 20)
    if verbose:
        print('chromosomes are')
        print(population)
        print("================")
        input()
    probs = []
    for chromosome in population:
        #draw(chromosome)
        prob = evaluator(chromosome)
        probs.append(prob)
        #print(prob)
    fitness = 1 / np.asarray(probs)
    sum = np.sum(fitness)
    print('sum', sum)
    probs = fitness / sum
    probs = probs.tolist()
    print(probs)
    if verbose:
        print("prob is")
        print(probs)
        print("================")
    population = np.asarray(population)
    distance = []
    for i in range(5000):
        print(i)
        parents = roulette_wheel_selection(population, probs, 10)
        if verbose:
            print('selected parents are')
            print(parents)
            print("================")
            input()
        children = generate_child(parents)
        if verbose:
            print('children are')
            print(children)
            print(len(children))
            print("================")
            input()
        population = population.tolist()
        for child in children:
            population.append(child)
        probs = []
        for chromosome in population:
            prob = evaluator(chromosome)
            probs.append(prob)
        distance = probs.copy()
        fitness = 1 / np.asarray(probs)
        sum = np.sum(fitness)
        probs = fitness / sum
        probs = probs.tolist()
        if debug:
            pdb.set_trace()
        population, probs = population_selection(population, probs)
        if verbose:
            print('new populatin is:')
            print(population)
            print("================")
            print(len(population))
            input()
        if verbose:
            print('prob is')
            print(probs)
            print("================")
            print(len(probs))
            input()
    print('min distance found is')
    print(np.min(distance))

if __name__ == "__main__":
    main()


# SPDX-License-Identifier: MIT

from __future__ import annotations

from typing import TYPE_CHECKING, List, Optional

from .enums import (
    AutoModerationActionType,
    AutoModerationEventType,
    AutoModerationTriggerType,
    KeywordPresetType,
    try_enum,
)
from .errors import InvalidArgument
from .mixins import Hashable
from .object import Object
from .utils import MISSING, get_as_snowflake

if TYPE_CHECKING:
    from .abc import GuildChannel, Snowflake
    from .guild import Guild
    from .member import Member
    from .message import Message
    from .role import Role
    from .state import ConnectionState
    from .types.auto_moderation import (
        AutoModerationAction as AutoModerationActionPayload,
        AutoModerationActionExecution as ActionExecutionPayload,
        AutoModerationActionMetadata as ActionMetadataPayload,
        AutoModerationRule as AutoModerationRulePayload,
        AutoModerationRuleModify,
        AutoModerationTriggerMetadata as TriggerMetadataPayload,
    )

__all__ = (
    "AutoModerationTriggerMetadata",
    "AutoModerationActionMetadata",
    "AutoModerationAction",
    "AutoModerationRule",
    "AutoModerationActionExecution",
)

class AutoModerationTriggerMetadata:
    """Represents data about an auto moderation trigger rule.

    .. versionadded:: 2.1

    Attributes
    ----------
    keyword_filter: List[:class:`str`]
        A list of substrings which will be searched for in content.

        .. note::

            This is ``None`` and cannot be provided if the trigger type of the rule is not
            :attr:`AutoModerationTriggerType.keyword`.
    regex_patterns: Optional[List[:class:`str`]]
        A list of regex patterns which will be matched with content. Can only be up to 10 patterns.

        .. versionadded:: 2.4

        .. note::

            This is ``None`` and cannot be provided if the trigger type of the rule is not
            :attr:`AutoModerationTriggerType.keyword`.

        .. warning::

            The flavor of regex used is Rust flavor and has no guarantees of working with
            the :mod:`re` module.
            Each regex pattern must be 75 characters or less.

    presets: List[:class:`KeywordPresetType`]
        A list of Discord pre-defined wordsets which will be searched for in content.

        .. note::

            This is ``None`` and cannot be provided if the trigger type of the rule is not
            :attr:`AutoModerationTriggerType.keyword_preset`.
    allow_list: List[:class:`str`]
        A list of exempt strings that will not trigger the preset type.

        .. note::

            This is ``None`` and cannot be provided if the trigger type of the rule is not
            :attr:`AutoModerationTriggerType.keyword_preset`.

        .. warning::

            Wildcard syntax (`*`) is not supported here.
    mention_total_limit: Optional[:class:`int`]
        The maximum amount of mentions allowed in a messsage.

        .. versionadded:: 2.3

        .. note::

            This is ``None`` and cannot be provided if the trigger type of this rule is not :attr:`AutoModerationTriggerType.mention_spam`.
    mention_raid_protection_enabled: Optional[:class:`bool`]
        Whether to automatically detect mention raids.

        .. versionadded:: 2.6

        .. note::

            This is ``None`` and cannot be provided if the trigger type of this rule is not :attr:`AutoModerationTriggerType.mention_spam`.
    """

    __slots__ = (
        "keyword_filter",
        "regex_patterns",
        "presets",
        "allow_list",
        "mention_total_limit",
        "mention_raid_protection_enabled",
    )

    def __init__(
        self,
        *,
        keyword_filter: Optional[List[str]] = None,
        regex_patterns: Optional[List[str]] = None,
        presets: Optional[List[KeywordPresetType]] = None,
        allow_list: Optional[List[str]] = None,
        mention_total_limit: Optional[int] = None,
        mention_raid_protection_enabled: Optional[bool] = None,
    ) -> None:
        self.keyword_filter: Optional[List[str]] = keyword_filter
        self.regex_patterns: Optional[List[str]] = regex_patterns
        self.presets: Optional[List[KeywordPresetType]] = presets
        self.allow_list: Optional[List[str]] = allow_list
        self.mention_total_limit: Optional[int] = mention_total_limit
        self.mention_raid_protection_enabled: Optional[bool] = mention_raid_protection_enabled

    @classmethod
    def from_data(cls, data: TriggerMetadataPayload):
        keyword_filter = data.get("keyword_filter")
        regex_patterns = data.get("regex_patterns")
        presets = (
            [try_enum(KeywordPresetType, preset) for preset in data["presets"]]
            if "presets" in data
            else None
        )
        allow_list = data.get("allow_list")
        mention_total_limit = data.get("mention_total_limit")
        mention_raid_protection_enabled = data.get("mention_raid_protection_enabled")

        return cls(
            keyword_filter=keyword_filter,
            regex_patterns=regex_patterns,
            presets=presets,
            allow_list=allow_list,
            mention_total_limit=mention_total_limit,
            mention_raid_protection_enabled=mention_raid_protection_enabled,
        )

    @property
    def payload(self) -> TriggerMetadataPayload:
        payload: TriggerMetadataPayload = {}

        if self.keyword_filter is not None:
            payload["keyword_filter"] = self.keyword_filter

        if self.regex_patterns is not None:
            payload["regex_patterns"] = self.regex_patterns

        if self.presets is not None:
            payload["presets"] = [enum.value for enum in self.presets]

        if self.allow_list is not None:
            payload["allow_list"] = self.allow_list

        if self.mention_total_limit is not None:
            payload["mention_total_limit"] = self.mention_total_limit

        if self.mention_raid_protection_enabled is not None:
            payload["mention_raid_protection_enabled"] = self.mention_raid_protection_enabled

        return payload

class AutoModerationActionMetadata:
    """Represents additional data that is used when an action is executed.

    .. versionadded:: 2.1

    Attributes
    ----------
    channel: Optional[:class:`abc.Snowflake`]
        The channel to which message content should be logged.

        .. note::

            This is ``None`` and cannot be provided if the action type of the rule is not
            :attr:`AutoModerationActionType.send_alert_message`.
    duration_seconds: Optional[:class:`int`]
        The duration of the timeout in seconds.

        .. note::

            This is ``None`` and cannot be provided if the action type of the rule is not
            :attr:`AutoModerationActionType.timeout`.

        .. note::

            The maximum value that can be used is ``2419200`` seconds (4 weeks)
    """

    __slots__ = ("channel_id", "duration_seconds")

    def __init__(
        self, *, channel: Optional[Snowflake] = None, duration_seconds: Optional[int] = None
    ) -> None:
        self.channel_id: Optional[int] = channel.id if channel is not None else None
        self.duration_seconds: Optional[int] = duration_seconds

    @classmethod
    def from_data(cls, data: ActionMetadataPayload):
        channel_id = get_as_snowflake(data, "channel_id")
        channel = Object(id=channel_id) if channel_id is not None else None
        duration_seconds = data.get("duration_seconds")

        return cls(channel=channel, duration_seconds=duration_seconds)

    @property
    def payload(self) -> ActionMetadataPayload:
        payload: ActionMetadataPayload = {}

        if self.channel_id is not None:
            payload["channel_id"] = self.channel_id

        if self.duration_seconds is not None:
            payload["duration_seconds"] = self.duration_seconds

        return payload

class AutoModerationAction:
    """Represents an auto moderation action that will execute whenever a rule is triggered.

    .. versionadded:: 2.1

    Parameters
    ----------
    type: :class:`AutoModerationActionType`
        The type to use for this action.
    metadata: :class:`AutoModerationActionMetadata`
        The additional data to use during execution of this action.

    Attributes
    ----------
    type: :class:`AutoModerationActionType`
        The type of this action.
    metadata: :class:`AutoModerationActionMetadata`
        The additional metadata needed during execution for this specific action type.
    """

    __slots__ = ("type", "metadata")

    def __init__(
        self,
        *,
        type: AutoModerationActionType,
        metadata: Optional[AutoModerationActionMetadata] = None,
    ) -> None:
        self.type: AutoModerationActionType = type
        self.metadata: Optional[AutoModerationActionMetadata] = metadata

    @classmethod
    def from_data(cls, data: AutoModerationActionPayload) -> AutoModerationAction:
        type = try_enum(AutoModerationActionType, data["type"])
        metadata = AutoModerationActionMetadata.from_data(data.get("metadata", {}))
        return cls(type=type, metadata=metadata)

    @property
    def payload(self) -> AutoModerationActionPayload:
        data: AutoModerationActionPayload = {
            "type": self.type.value,
        }

        if self.metadata is not None:
            data["metadata"] = self.metadata.payload

        return data

class AutoModerationRule(Hashable):
    """Represents a Discord auto moderation rule.

    .. versionadded:: 2.1

    .. container:: operations

        .. describe:: x == y

            Checks if two rules are equal.

        .. describe:: x != y

            Checks if two rules are not equal.

        .. describe:: hash(x)

            Returns the rules's hash.

        .. describe:: str(x)

            Returns the rules's name.

    Attributes
    ----------
    id: :class:`int`
        The rule's unique ID.
    guild_id: :class:`int`
        The guild's unique ID which this rule belongs to.
    guild: Optional[:class:`Guild`]
        The guild which this rule belongs to, if found in cache.
    name: :class:`str`
        The rule's name.
    creator_id: :class:`int`
        The user's unique ID which first created this rule.
    creator: Optional[:class:`Member`]
        The member which first created this rule, if found in cache.
    event_type: :class:`AutoModerationEventType`
        The event context in which this rule is checked.
    trigger_type: :class:`AutoModerationTriggerType`
        The type of content that can trigger this rule.
    trigger_metadata: :class:`AutoModerationTriggerMetadata`
        Additional data that is used when checking if this rule is triggered.
    actions: List[:class:`AutoModerationAction`]
        The actions which will execute when the rule is triggered.
    enabled: :class:`bool`
        Whether the rule is enabled.
    exempt_role_ids: List[:class:`int`]
        The role ids that should not be affected by the rule.
    exempt_channel_ids: List[:class:`int`]
        The channel ids that should not be affected by the rule.
    exempt_roles: List[:class:`Role`]
        The roles that should not be affected by the rule, if found in cache.
    exempt_channels: List[:class:`abc.GuildChannel`]
        The channels that should not be affected by the rule, if found in cache.
    """

    __slots__ = (
        "_state",
        "id",
        "guild_id",
        "guild",
        "name",
        "creator_id",
        "creator",
        "event_type",
        "trigger_type",
        "trigger_metadata",
        "actions",
        "enabled",
        "exempt_role_ids",
        "exempt_channel_ids",
        "exempt_roles",
        "exempt_channels",
    )

    def __init__(self, *, data: AutoModerationRulePayload, state: ConnectionState) -> None:
        self._state = state
        self.id: int = int(data["id"])
        self.guild_id: int = int(data["guild_id"])
        self.guild: Optional[Guild] = state._get_guild(self.guild_id)
        self.name: str = data["name"]
        self.creator_id: int = int(data["creator_id"])
        self.creator: Optional[Member] = (
            self.guild.get_member(self.creator_id) if self.guild is not None else None
        )
        self.event_type: AutoModerationEventType = try_enum(
            AutoModerationEventType, data["event_type"]
        )
        self.trigger_type: AutoModerationTriggerType = try_enum(
            AutoModerationTriggerType, data["trigger_type"]
        )
        self.trigger_metadata: AutoModerationTriggerMetadata = (
            AutoModerationTriggerMetadata.from_data(data["trigger_metadata"])
        )
        self.actions: List[AutoModerationAction] = [
            AutoModerationAction.from_data(data=action) for action in data["actions"]
        ]
        self.enabled: bool = data["enabled"]
        self.exempt_role_ids: List[int] = [int(role_id) for role_id in data["exempt_roles"]]
        self.exempt_channel_ids: List[int] = [
            int(channel_id) for channel_id in data["exempt_channels"]
        ]
        self.exempt_roles: List[Optional[Role]] = (
            [self.guild.get_role(role_id) for role_id in self.exempt_role_ids]
            if self.guild is not None
            else []
        )
        self.exempt_channels: List[Optional[GuildChannel]] = (
            [self.guild.get_channel(channel_id) for channel_id in self.exempt_channel_ids]
            if self.guild is not None
            else []
        )

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

    async def delete(self, *, reason: Optional[str] = None) -> None:
        """|coro|

        Delete the current rule from its guild.

        Requires the :attr:`~Permissions.manage_guild` permission.

        Parameters
        ----------
        reason: Optional[:class:`str`]
            The reason for deleting this rule. Shows up on the audit log.

        Raises
        ------
        Forbidden
            You do not have permission to delete this rule.
        HTTPException
            Deleting the rule failed.
        """

        await self._state.http.delete_auto_moderation_rule(self.guild_id, self.id, reason=reason)

    async def edit(
        self,
        *,
        name: str = MISSING,
        event_type: AutoModerationEventType = MISSING,
        trigger_metadata: AutoModerationTriggerMetadata = MISSING,
        actions: List[AutoModerationAction] = MISSING,
        enabled: bool = MISSING,
        exempt_roles: List[Snowflake] = MISSING,
        exempt_channels: List[Snowflake] = MISSING,
        reason: Optional[str] = None,
    ) -> AutoModerationRule:
        """Modify the current auto moderation rule.

        Requires the :attr:`~Permissions.manage_guild` permission.

        Parameters
        ----------
        name: :class:`str`
            The new name for this rule.
        event_type: :class:`AutoModerationEventType`
            The new event context in which this rule is checked.
        trigger_metadata: :class:`AutoModerationTriggerMetadata`
            The new additional data that is used when checking if this rule is triggered.
        actions: List[:class:`AutoModerationAction`]
            The new actions which will execute when the rule is triggered.
        enabled: :class:`bool`
            Whether the rule is enabled.
        exempt_roles: List[:class:`abc.Snowflake`]
            The new roles that should not be affected by the rule.
        exempt_channels: List[:class:`abc.Snowflake`]
            The new channels that should not be affected by the rule.
        reason: Optional[:class:`str`]
            The reason for editing this rule. Shows in the audit log.

        Raises
        ------
        Forbidden
            You do not have permission to edit this rule.
        HTTPException
            Editing the rule failed.
        InvalidArgument
            An incorrect type was passed.

        Returns
        -------
        :class:`AutoModerationRule`
            The newly edited auto moderation rule.
        """

        payload: AutoModerationRuleModify = {}

        if name is not MISSING:
            payload["name"] = name

        if event_type is not MISSING:
            if not isinstance(event_type, AutoModerationEventType):
                raise InvalidArgument("event_type must be of type AutoModerationEventType")

            payload["event_type"] = event_type.value

        if trigger_metadata is not MISSING:
            if not isinstance(trigger_metadata, AutoModerationTriggerMetadata):
                raise InvalidArgument(
                    "trigger_metadata must be of type AutoModerationTriggerMetadata"
                )

            payload["trigger_metadata"] = trigger_metadata.payload

        if actions is not MISSING:
            payload["actions"] = [action.payload for action in actions]

        if enabled is not MISSING:
            payload["enabled"] = enabled

        if exempt_roles is not MISSING:
            payload["exempt_roles"] = [str(role.id) for role in exempt_roles]

        if exempt_channels is not MISSING:
            payload["exempt_channels"] = [str(channel.id) for channel in exempt_channels]

        data = await self._state.http.modify_auto_moderation_rule(
            self.guild_id, self.id, data=payload, reason=reason
        )
        return AutoModerationRule(data=data, state=self._state)

class AutoModerationActionExecution:
    """Represents the execution of an auto moderation action

    .. versionadded:: 2.1

    Attributes
    ----------
    guild_id: :class:`int`
        The guild ID where this action was executed.
    guild: Optional[:class:`Guild`]
        The guild where this action was executed, if it was found in cache.
    channel_id: Optional[:class:`int`]
        The channel ID where this action was executed, if applicable.
    channel: Optional[:class:`abc.GuildChannel`]
        The channel where this action was executed, if applicable and found in cache.
    message_id: Optional[:class:`int`]
        The message ID that executed this action, if it was not blocked.
    message: Optional[:class:`Message`]
        The message that executed this action, if it was not blocked and found in cache.
    alert_system_message_id: Optional[:class:`int`]
        The ID of the system alert message, if sent.
    alert_system_message: Optional[:class:`Message`]
        The system alert message, if sent and found in cache.
    action: :class:`AutoModerationAction`
        The action that was executed.
    rule_id: :class:`int`
        The id of the rule that was executed.
    rule_trigger_type: :class:`AutoModerationTriggerType`
        The type of rule that was executed.
    member_id: :class:`int`
        The ID of the user that triggered this action.
    member: Optional[:class:`Member`]
        The member that triggered this action, if found in cache.
    content: :class:`str`
        The content the user sent in the message

        .. note::

            This requires :attr:`Intents.message_content` to not be empty.
    matched_keyword: Optional[:class:`str`]
        The keyword configured in the rule that matched this message, if applicable.
    matched_content: Optional[:class:`str`]
        The content in the message that matched the keyword, if applicable.

        .. note::

            This requires :attr:`Intents.message_conrent` to not be empty.
    """

    def __init__(self, *, data: ActionExecutionPayload, state: ConnectionState) -> None:
        self.guild_id: int = int(data["guild_id"])
        self.guild: Optional[Guild] = state._get_guild(self.guild_id)
        self.channel_id: Optional[int] = get_as_snowflake(data, "channel_id")
        if self.guild is not None and self.channel_id is not None:
            self.channel: Optional[GuildChannel] = self.guild.get_channel(self.channel_id)
        else:
            self.channel: Optional[GuildChannel] = None
        self.message_id: Optional[int] = get_as_snowflake(data, "message_id")
        self.message: Optional[Message] = state._get_message(self.message_id)
        self.alert_system_message_id: Optional[int] = get_as_snowflake(
            data, "alert_system_message_id"
        )
        self.alert_system_message: Optional[Message] = state._get_message(
            self.alert_system_message_id
        )
        self.action: AutoModerationAction = AutoModerationAction.from_data(data=data["action"])
        self.rule_id: int = int(data["rule_id"])
        self.rule_trigger_type: AutoModerationTriggerType = try_enum(
            AutoModerationTriggerType, data["rule_trigger_type"]
        )
        self.member_id: int = int(data["user_id"])
        self.member: Optional[Member] = (
            self.guild.get_member(self.member_id) if self.guild is not None else None
        )
        self.content: str = data.get("content", "")
        self.matched_keyword: Optional[str] = data["matched_keyword"]
        self.matched_content: Optional[str] = data.get("matched_content", "")


from astropy.io import fits
from astropy.wcs import WCS
import numpy as np
from matplotlib import pyplot as plt
from typing import Tuple

def wcs_from_fits(filename: str, index: int = 0, extract_data: bool = False) -> WCS:
    """
    Extract WCS object (and data as arrays) from fits file.
    """
    w = None
    d = []
    with fits.open(filename) as hdul:
        w = WCS(hdul[index].header)
        if extract_data:
            for i in range(len(hdul)):
                d.append(np.array(hdul[i].data))
    if extract_data:
        return w, d
    return w

def figure_wcs(w: WCS, figsize: Tuple[int] = (10, 10)):
    """
    Create figure and axis with WCS projection.
    """
    fig = plt.figure(figsize=figsize)
    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], projection=w)
    ra = ax.coords[0]
    ra.set_format_unit('degree')
    return fig, ax

def show_fits(filename: str, fun: callable = None, data_index: int = 0,
              figsize: Tuple[int] = (10, 10), vmin=None, vmax=None, sparse: bool = False):
    """
    Show data from fits file with its WCS projection.
    """
    w, d = wcs_from_fits(filename, extract_data=True)
    if fun is not None:
        d = fun(d)
    fig, ax = figure_wcs(w, figsize=figsize)

    if not sparse:
        ax.imshow(d[data_index], cmap=plt.get_cmap('viridis'),
                  interpolation='nearest', vmin=vmin, vmax=vmax)
    else:
        ax.spy(d[data_index], cmap=plt.get_cmap('viridis'))
    return fig, ax


from rest_framework import generics, permissions
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db.models import Q

from parties.models import Party
from parties.api.pagination import StandardResultsPagination
from parties.api.serializers import PartyModelSerializer
from hashtags.models import HashTag

# this creates the api view that our tags page pulls from
# it holds tweets only, no users
# Right now, you can only access the tag page by clicking on a hashtagged item
# it's not heavily used now, but I think we can use it to tag games
class TagPartyAPIView(generics.ListAPIView):
	queryset = Party.objects.all().order_by('-time_created')
	# because the tags are all in the text of the events,
	# the information passed to the API is just the same os the normal party
	serializer_class = PartyModelSerializer
	pagination_class = StandardResultsPagination

	# this allows us to pass a request into the serializer
	# it'll tell us if the user starred the event so we can display the
	# correct verb at the outset
	def get_serializer_context(self, *args, **kwargs):
		context = super(TagPartyAPIView, self).get_serializer_context(*args, **kwargs)
		context['request'] = self.request
		return context

	# this interacts with the call to this url and does the actual search
	# for the events containing the tag
	def get_queryset(self, *args, **kwargs):
		# gets the passed hashtag
		hashtag = self.kwargs.get('hashtag')
		hashtag_obj = None
		try:
			hashtag_obj = HashTag.objects.get_or_create(tag=hashtag)[0]
		except:
			pass
		if hashtag_obj:
			# gets all parties with hashtags matching the one passed in
			# it will return any with the hashtag in the title or description
			qs = hashtag_obj.get_parties()
			# query is currently unused
			query = self.request.GET.get('q', None)
			if query is not None:
				# this looks for tags in username, description, and title
				qs = qs.filter(
					Q(description__icontains=query) |
					Q(user__username__icontains=query) |
					Q(title__icontains=query)
					)
			return qs
		return None


import tensorflow as tf
import os
import sys
import argparse

def scheduler(epoch,lr):
    if epoch < 70:
        return lr
    elif epoch < 100:
        ret = lr/1.01
        return ret
    else:
        ret = lr/1.001
        return ret

def main(args):

    # Get number of classes for training
    num_classes = len(os.listdir(args.dataset_path))

    # Create data train and validation from directory
    datagen_train = tf.keras.preprocessing.image_dataset_from_directory(args.dataset_path, batch_size=args.batch_size,seed=args.seed_random,
                                                                labels='inferred', label_mode='categorical',image_size=(args.image_size,args.image_size),
                                                                validation_split=args.validation_split,subset="training")
    datagen_val   = tf.keras.preprocessing.image_dataset_from_directory(args.dataset_path, batch_size=args.batch_size,seed=args.seed_random,
                                                                labels='inferred', label_mode='categorical', image_size=(args.image_size, args.image_size),
                                                                validation_split=args.validation_split, subset="validation")

    # Create facenet model with L2 embeddings
    model = tf.keras.models.load_model(args.model_path)
    L2_normalization = tf.keras.layers.Lambda(lambda x: tf.math.l2_normalize(x, axis = 1), name='L2_normalization')(model.output)
    predictions = tf.keras.layers.Dense(num_classes, activation='softmax',name='Predictions')(L2_normalization)
    model = tf.keras.models.Model(inputs=[model.input], outputs=[predictions])
    out_model=os.path.join(os.path.split(args.model_path)[0],'softmax_keras_facenet_model.h5')
    model.save(out_model)

    # Rescaling dataset
    AUTOTUNE = tf.data.AUTOTUNE

    train_ds = datagen_train.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
    val_ds = datagen_val.cache().prefetch(buffer_size=AUTOTUNE)

    normalization_layer = tf.keras.layers.experimental.preprocessing.Rescaling(1./255)
    normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
    normalized_vds= val_ds.map(lambda x, y: (normalization_layer(x), y))

    # Create checkpoint
    if args.model_checkpoint_path is not None:
        MODEL_CHECKPOINT_PATH=os.path.join(args.model_checkpoint_path,'model_softmax_checkpoint.h5')
    else:
        MODEL_CHECKPOINT_PATH=os.path.join(os.path.split(args.model_path)[0],'model_softmax_checkpoint.h5')
    mcp = tf.keras.callbacks.ModelCheckpoint(MODEL_CHECKPOINT_PATH, monitor="val_accuracy",
                                             save_best_only=True, save_weights_only=True)

    lr_decrease = tf.keras.callbacks.LearningRateScheduler(scheduler)

    if args.model_logs_path is not None:
        tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=args.model_logs_path, histogram_freq=1)
        callbacks=[mcp,lr_decrease,tensorboard_callback]
    else:
        callbacks=[mcp,lr_decrease]

    # Compile model
    model.compile(optimizer=tf.keras.optimizers.Adam(args.learning_rate),loss='categorical_crossentropy',metrics=['accuracy'])

    # Training
    model.fit(normalized_ds,validation_data=normalized_vds,validation_freq=args.validation_freq, epochs=args.epochs,callbacks=callbacks)

def parse_arguments(argv):
    parser = argparse.ArgumentParser()

    parser.add_argument('model_path', type=str,
                        help='Path to the model keras (inception resnet v1) (.h5 file)')

    parser.add_argument('dataset_path', type=str,
                        help='Path to the dataset directory')

    parser.add_argument('--model_checkpoint_path', type=str,
                        help='Path to the checkpoint directory', default=None)

    parser.add_argument('--image_size', type=int,
                        help='Image size of Facenet input', default=160)

    parser.add_argument('--model_logs_path', type=str,
                        help='Path to the logs directory', default=None)

    parser.add_argument('--learning_rate', type=float,
                        help='Default is 0.001', default=0.001)

    parser.add_argument('--epochs', type=int,
                        help='Default is 90', default=90)

    parser.add_argument('--batch_size', type=int,
                        help='Default is 32', default=32)

    parser.add_argument('--seed_random', type=int,
                        help='Default is 123', default=123)

    parser.add_argument('--validation_split', type=float,
                        help='Default is 0.1', default=0.1)

    parser.add_argument('--validation_freq', type=int,
                        help='Default is 1', default=1)

    return parser.parse_args(argv)

if __name__ == '__main__':
    main(parse_arguments(sys.argv[1:]))


"""add order tables

Revision ID: 2be110d333f1
Revises: 88359808368e
Create Date: 2021-05-22 10:31:41.772121

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '2be110d333f1'
down_revision = '88359808368e'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('orders',
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('customer_id', sa.BigInteger(), nullable=False),
    sa.Column('payment_type', sa.Enum('ONLINE', 'BANK_SLIP', name='orderpaymenttype'), nullable=False),
    sa.Column('status', sa.Enum('CREATED', 'PENDING', 'PAID', 'SENT', 'RECEIVED', 'CANCELLED', name='orderstatus'), nullable=False),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('order_items',
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('product_id', sa.BigInteger(), nullable=False),
    sa.Column('order_id', sa.BigInteger(), nullable=False),
    sa.Column('amount', sa.Numeric(), nullable=False),
    sa.Column('quantity', sa.Integer(), nullable=False),
    sa.ForeignKeyConstraint(['order_id'], ['orders.id'], ),
    sa.ForeignKeyConstraint(['product_id'], ['products.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('order_items')
    op.drop_table('orders')
    op.execute("DROP TYPE orderpaymenttype")
    op.execute("DROP TYPE orderstatus")
    # ### end Alembic commands ###


import re
import json

import urllib3
from es import es_updater
from es import conf
from es import question_classification_interface
import numpy as np

def pre_sentence(seg_lst, pos_lst, lower=True, ignore_punctuation=True, ignore_stopword=True, with_pos=True):
    """
    预处理句子
    :param seg_lst: 句子分词结果
    :param lower:
    :param ignore_punctuation:
    :param ignore_stopword:
    :param with_pos:
    :return: [句子处理结果, 词性标注结果(非必须)]
        样例如下：
        问题: "今天天气怎么样？HELLO world，嗯，Oh no！@# ~"
        结果: [('今天', '天气', 'hello', 'world', 'oh', 'no') ('nt', 'n', 'ws', 'ws', 'ws', 'ws')]
    """

    seg_pos_pair_lst = list(zip(seg_lst, pos_lst))

    # 小写
    if lower:
        seg_pos_pair_lst = map(lambda pair: (pair[0].lower(),) if len(pair) <= 1 else (pair[0].lower(), pair[1]),
                               seg_pos_pair_lst)

    # 去标点
    if ignore_punctuation:
        seg_pos_pair_lst = filter(lambda pair: False if is_punctuation(pair[0]) else True, seg_pos_pair_lst)

    # 去停用词
    if ignore_stopword:
        seg_pos_pair_lst = filter(lambda pair: False if is_stopword(pair[0]) else True, seg_pos_pair_lst)

    result = list(seg_pos_pair_lst)
    if len(result) == 0:
        if with_pos:
            return [(), ()]
        else:
            return [()]
    else:
        return list(zip(*result))

def is_punctuation(
        word,
        pattern=re.compile(r"^[\~!@#\$%\^&\*\(\)\-_\+=\{\}\[\]\|\\:;\"'<,>\.\?/～！￥…×（）—【】、：；“”‘’《》，。？]+$")
):
    """
    判断word是否属于标点
    :param word:
    :param pattern:
    :return:
    """
    if pattern.match(word):
        return True
    return False

def is_stopword(word):
    """
    判断是否是停用词
    :param word: string
    :return: True/False
    """
    if word in STOPWORDS:
        return True
    return False

def get_stopwords(file="./data/ext_stopword.dic"):
    """
    获取停用词
    :param file:
    :return:
    """
    with open(file, encoding='utf8') as f:
        lines = f.readlines()
        return set(map(lambda str: str.strip("\n"), lines))

# 加载停用词
try:
    STOPWORDS = get_stopwords()
except:
    STOPWORDS = get_stopwords("./es/data/ext_stopword.dic")

HTTP = urllib3.PoolManager()

def count_with_one_tenant(tenant_id):
    """
    统计一个tenant下的数据数量
    :param tenant_id:
    :return:
    """
    r = HTTP.request("GET", "%s/%s/%s/_count" % (conf.ES_URL, conf.ES_INDEX_NAME, tenant_id))
    if r.status != 200:
        # 如果ES发生错误，尽快让问题显现
        print("es error.")
        return None
    else:
        data = json.loads(r.data.decode())
        return data["count"]

def full_text_search(tenant_id, raw_question, top_size=128, main_que_type_lst=[]):
    # 需要对es检索进行改造, 现在保证 所有在es里面的记录都含有 main_que_type字段  类型是int
    # if len(main_que_type_lst) < 1:    则是在tenant_id内的所有记录检索
    # else:                             则是在tenant_id内的 并且 main_que_type 在main_que_type_lst列表内的记录 进行检索

    """
    初步筛选
    :param tenant_id:
    :param raw_question:
    :param top_pct:
    :return:
    """
    #print("full_text_search tid:%d que:%s" % (tenant_id, raw_question))
    seg_lst_from_i, pos_lst_from_i = es_updater.analyze_sentence_from_ltp(raw_question)

    print(seg_lst_from_i, pos_lst_from_i)

    seg_lst, pos_lst = pre_sentence(seg_lst_from_i, pos_lst_from_i) #在归一化es分数时用到

    seg_lst_from_i = json.dumps(seg_lst_from_i, ensure_ascii=False)
    pos_lst_from_i = json.dumps(pos_lst_from_i, ensure_ascii=False)

    intention_from_i = question_classification_interface.get_intention_all(raw_question)
    print(intention_from_i)
    # 生成main_que_type限制集
    mqt_limit_lst = list()
    for number in main_que_type_lst:
        mqt_limit_lst.append({
                "term": {
                    # 经过实验，ES会对数字类型转换成string类型进行匹配，所以这个没有严格的类型要求。
                    # 如果这个字段不存在，并且外部传入限制条件时，无法查询出任何数据。
                    "main_que_type": number
                }
            })

    query_body = {
        "from": 0,
        "size": top_size,
        "min_score": 0.0,
        "explain": False,
        "sort": {
            "_score": {
                "order": "desc"
            }
        },
        "query": {
            "bool": {
                "must": [
                    {
                        "match": {
                            "raw-question.imw": {
                                "query": raw_question,
                                "analyzer": "ik-max-word-with-synonym"
                            }
                        }
                    },
                    {
                        "bool": {
                            "should": mqt_limit_lst
                        }
                    }
                ]
            }
        }
    }

    #print(query_body)

    r = HTTP.request("POST",
                     "%s/%s/%s/_search?search_type=dfs_query_then_fetch" % (conf.ES_URL, conf.ES_INDEX_QUERY_NAME, tenant_id),
                     body=json.dumps(query_body).encode("utf-8"))

    if r.status != 200:
        print("es return error")
        return []

    es_res = json.loads(r.data.decode())
    es_qa_info = es_res["hits"]["hits"]
    # #print(es_qa_info)
    es_res_len = len(es_qa_info)

    if es_res_len == 0:
        return []

    result_num = es_res_len  # int(np.ceil(es_res_len * top_pct))

    # 数据量太少的情况 特殊处理
    # if result_num < 3:
    #    result_num = es_res_len

    final_res = list()

    #### 尼玛防止结果重复, key raw-question, value qa-id, 对于同一个raw-question, 只保留qa-id最大的那个
    avoid_dup_map = dict()
    for e in es_qa_info[0: result_num]:
        _source = e["_source"]
        key_str = _source["raw-question"]
        value_id = _source["qa-id"]
        if key_str not in avoid_dup_map:
            avoid_dup_map[key_str] = value_id
        else:
            if value_id > avoid_dup_map[key_str]:
                avoid_dup_map[key_str] = value_id

    #只返回设置答案格式的内容，默认为全选。
    # content_type = conf_algorithm.get_value(tenant_id, "CONTENT_TYPE")

    for e in es_qa_info[0: result_num]:
        _source = e["_source"]

        key_str = _source["raw-question"]
        value_id = _source["qa-id"]

        main_que_type = 0
        if 'main_que_type' in _source:
            main_que_type = int(_source['main_que_type'])

        if not ((key_str in avoid_dup_map) and (value_id == avoid_dup_map[key_str])):
            continue

        # if _source["content-type"] not in content_type:
        #     #print("filter content-type: " + _source["content-type"] + str(_source["raw-question"]))
        #     continue

        know_base_len = count_with_one_tenant(tenant_id)
        es_score_nor = es_score_normalize(e["_score"], know_base_len, len(seg_lst))

        #针对出来的答案是utf_8二进制码，尝试先解码在存入：20170811,qxh
        # try:
        #     raw_anwer = json.loads(_source["raw-answer"])
        # except:
        #     raw_anwer = _source["raw-answer"]

        element_dict = {
            "tenant-id": _source["tenant-id"],
            "qa-id": _source["qa-id"],
            "score": es_score_nor,
            "pre-question-from-i": seg_lst_from_i,
            "pos-from-i": pos_lst_from_i,
            "pre-question-from-m": _source["pre-ltp-question"],
            "pos-from-m": _source["pos-ltp"],
            "raw-question": _source["raw-question"],
            "raw-answer": _source["raw-answer"],
            "raw-question-from-i": raw_question,
            "content_type": _source["content-type"],
            "qa-id-ext": _source["qa-id-ext"],
            "intention_from_i": json.dumps(intention_from_i, ensure_ascii=False),
            "intention_from_m": _source["intention_from_m"],
            "main_que_type": main_que_type,
        }

        # 紧急添加，以后删除 ----
        if element_dict["tenant-id"] == 46 and element_dict["content_type"] == "text":
            a = element_dict["raw-answer"]
            if a == "":
                continue
            try:
                aa = json.loads(a)
                if aa["answer"] == "":
                    continue
            except Exception as e:
                continue
        # 紧急添加，以后删除 ----

        final_res.append(element_dict)

    return final_res

def es_score_mean(s, N, n):
    """
    es平均分数
    """
    if N == None:
        #print("warning: input tenant base len is None!")
        return 0
    if n < 1:
        #print("warning: input word num are small than 1!")
        return 0
    if s < 0:
        #print("warning: input es score is small than 0!")
        return 0

    return s / np.log(N) / n

def es_score_normalize(s, N, n):
    """
    es分数规整
    """
    score_mean_average = 0.5
    score_mean_variance = 0.5
    score_mean = es_score_mean(s, N, n)
    score_mean = (score_mean - score_mean_average) / score_mean_variance
    score_nor = 1 / (1 + np.exp(-score_mean))
    #print("es score: " + str(score_nor))
    return score_nor

if __name__ == '__main__':
    xx = full_text_search(4, "钢琴学多久可以考级?考级是如何进行的？")
    #print(xx)


# N x N 복도
# 1 x 1 크기의 칸
# 선생님 T, 학생 S, 장애물 O
# 학생들은 선생님 감시에 들키지 않고 나가야 한다.
# 선생님 감시 -> 상하좌우, 장애물 뒤편에 있는 학생 볼 수 없음

from collections import deque

n = int(input())

lst = []
for _ in range(n):
	lst.append(list(input().split()))

def check():
	answer = True
	q = deque()
	for i in range(n):
		for j in range(n):
			if lst[i][j] == 'S':
				q.append((i, j))
				width = True
				vertical = True
				while q:
					l, k = q.popleft()
					if lst[l][k] == 'T':
						answer = False
						break
					if lst[l][k] == 'O':
						if l > i:
							vertical = False
						if k > j:
							width = False
					if vertical and l + 1 < n:
						q.append((l + 1, j))
					if width and  k + 1 < n:
						q.append((l, k + 1))
	return answer

ans = False

def dfs(iter):
	global ans
	if iter == 3:
		if check():
			ans = True
			return
		else:
			return
	for i in range(n):
		for j in range(n):
			if lst[i][j] == 'X':
				lst[i][j] = 'O'
				dfs(iter + 1)
				lst[i][j] = 'X'
dfs(0)

if ans:
	print('YES')
else:
	print('NO')


from typing import List, Optional

from fastapi import HTTPException, status
from . import models
# from . models import HomeUser
from ..users.models import User

async def new_media_register(request, database) -> models.Media:
    new_media = models.Media(name=request.name, ip_address=request.ip_address, is_playing=request.is_playing)
    database.add(new_media)
    database.commit()
    database.refresh(new_media)
    return new_media

async def all_media(database) -> List[models.Media]:
    medias = database.query(models.Media).all()
    return medias

async def delete_media_by_id(media_id, database):
    database.query(models.Media).filter(models.Media.id == media_id).delete()
    database.commit()


from logical_operators import CheckSolution

# Given an integer x, return true if x is palindrome integer. An integer is a palindrome when it reads the same backward as forward.
# For example, 121 is palindrome while 123 is not.
# input x = 121 output True, input x = 10 output False, input x = 11 output True, input x = 100 output False

class TaskNineteen(CheckSolution):
    def __init__(self, task_name, homework_name):
        self.homework_name = homework_name
        super().__init__(task_name)

    def test_case_1(self, solution):
        answer = solution(121)
        expected = True

        result = {
            "input": ["121"],
            "answer": answer,
            "expected": expected,
            "isSolved": answer == expected
        }
        return result

    def test_case_2(self, solution):
        answer = solution(10)
        expected = False

        result = {
            "input": ["10"],
            "answer": answer,
            "expected": expected,
            "isSolved": answer == expected
        }
        return result

    def test_case_3(self, solution):
        answer = solution(11)
        expected = True

        result = {
            "input": ["11"],
            "answer": answer,
            "expected": expected,
            "isSolved": answer == expected
        }
        return result

    def test_case_4(self, solution):
        answer = solution(100)
        expected = False

        result = {
            "input": ["100"],
            "answer": answer,
            "expected": expected,
            "isSolved": answer == expected
        }
        return result

    def check(self, solution, tg_username):
        test_cases = [
            self.test_case_1(solution),
            self.test_case_2(solution),
            self.test_case_3(solution),
            self.test_case_4(solution)
        ]
        isSolved = [test_case["isSolved"] for test_case in test_cases]
        isSolved = all(isSolved)
        self.checking(tg_username, isSolved, self.homework_name)
        print("-" * 50)

        for i, test_case in enumerate(test_cases, 1):
            emoji = "✅" if test_case["isSolved"] else "❌"
            print(f"{emoji} Test: {i}")
            if not test_case["isSolved"]:
                print(f"input: {', '.join(test_case['input'])}")
                print(f"answer: {test_case['answer']}")
                print(f"expected: {test_case['expected']}")