import sys
import ply.yacc as yacc
from lexer import tokens
from lexer import reserved
from procedureDirectory import procedureDirectory, Variable
from quadrupleGenerator import quadrupleGenerator
from semanticCube import getResultingType

currentDirectory = procedureDirectory("global")
functionDirectory = None
instructions = quadrupleGenerator()

variableStack = []
seenType = None
parameterCounter = 0
parameterCounterInt = 0
parameterCounterDou = 0
parameterCounterStr = 0
Function_Or_Var_Name = []
arrayType = []

precedence =    (
                ('left', 'AND', 'OR'),
                ('left', '+', '-'),
                ('left', '*', '/')
                )

start = 'program'

def p_program(p):
    '''program  : program2 program3 main_ahead main_method'''
    print("Compilation completed...")
    # sys.exit(0);

def p_main_ahead(p):
    '''main_ahead :'''
    global instructions, currentDirectory
    while len(instructions.jumpStack) is not 0:
        pendingJump = instructions.popJumpStack()
        instructions.setQuadrupleResult(pendingJump, instructions.nextInstruction)
    name = 'main cheese'
    # print (functionId)
    currentDirectory.add_directory(name)
    currentDirectory = currentDirectory.get_directory(name)
    currentDirectory.Type = 'main'

def p_program2(p):
    '''program2 : variable_declaration program2
                | empty'''

def p_program3(p):
    '''program3 : method_declaration program3
                | empty'''

def p_variable_declaration(p):
    '''variable_declaration  : type variable_declaration2'''

def p_variable_declaration2(p):
    '''variable_declaration2 : variable_declarator variable_declaration3'''

def p_variable_declaration3(p):
    '''variable_declaration3 : COMMA variable_declaration2
                             | SEMICOLON'''

def p_variable_declarator(p):
    '''variable_declarator  : ID variable_declarator2 variable_declarator3'''

def p_variable_declarator2(p):
    '''variable_declarator2 : LSQUARE CSTINT RSQUARE
                            | empty'''
    global currentDirectory, instructions
    if p[1]:
        currentDirectory.add_variable(p[-2], seenType, p[2])
        variable = currentDirectory.get_variable(p[-2])
        if variable.Type is int:
            instructions.generateQuadruple('INI', variable.Address, variable.Dimension, 1)
        elif variable.Type is float:
            instructions.generateQuadruple('INI', variable.Address, variable.Dimension, 2)
        elif variable.Type is str:
            instructions.generateQuadruple('INI', variable.Address, variable.Dimension, 3)
    else:
        currentDirectory.add_variable(p[-2], seenType, 0)

def p_variable_declarator3(p):
    '''variable_declarator3 : '=' superexpression
                            | empty'''
    global instructions, currentDirectory
    if p[1]:
        variable = currentDirectory.get_variable(p[-3])
        if variable and variable.Dimension == 0:
            op1 = instructions.popOperand()
            result = variable
            operator = p[1]

            validType = getResultingType(operator, result.Type, op1.Type)
            if validType:
                operator = 'EQU'
                instructions.generateQuadruple(operator, op1, 0, result)
            else:
                print ("ERROR: Invalid types in line {}. Variable '{}' of type '{}' cannot store '{}'!".format(p.lineno(1), currentDirectory.get_variable(result.Name).Name, currentDirectory.get_variable(result.Name).Type, currentDirectory.get_variable(op1.Name).Name))
                raise SystemExit
        else:
            if variable.Dimension > 0:
                print("ERROR: Variable '{}' cannot be initialized since it is an array, in line {}!".format(p[-3], p.lineno(1)))
            else:
                print("ERROR: Variable '{}' not declared in this scope, in line {}!".format(p[-3], p.lineno(1)))
                raise SystemExit

def p_method_declaration(p):
    '''method_declaration : FUNC type ID seen_function_id '(' method_declaration2 method_declaration3'''
    global currentDirectory
    instructions.generateQuadruple('RET',0,0,0)
    currentDirectory = currentDirectory.parent

def p_seen_function_id(p):
    '''seen_function_id :'''
    global currentDirectory
    functionId = p[-1]
    # print (functionId)
    currentDirectory.add_directory(functionId)
    currentDirectory = currentDirectory.get_directory(functionId)
    currentDirectory.Type = seenType

def p_method_declaration2(p):
    '''method_declaration2 : parameter_list
                           | empty'''

def p_method_declaration3(p):
    '''method_declaration3 : ')' seen_method_start block_statement'''

def p_seen_method_start(p):
    '''seen_method_start :'''
    global currentDirectory, instructions
    currentDirectory.startAddress = instructions.nextInstruction

def p_parameter_list(p):
    '''parameter_list  : parameter parameter_list2
       parameter_list2 : COMMA parameter_list
                       | empty'''

def p_parameter(p):
    '''parameter : type ID'''
    global currentDirectory
    variableId = p[2]
    currentDirectory.parameters.append(variableId)
    currentDirectory.add_variable(variableId, seenType, 0)

def p_main_method(p):
    '''main_method : MAIN CHEESE '(' ')' block_statement'''

def p_type(p):
    '''type : INT
            | DOUBLE
            | STRING
            | VOID'''
    global seenType
    variableTypes = { 'int' : int, 'double' : float, 'string' : str, 'void' : 'void' }
    seenType = variableTypes[p[1]]

def p_variable_assignment(p):
    '''variable_assignment : ID id_or_array'''

def p_id_or_array(p):
    '''id_or_array : '=' superexpression
                   | LSQUARE seen_LSQUARE superexpression RSQUARE seen_RSQUARE '=' superexpression'''
    global instructions, currentDirectory

    variable = currentDirectory.get_variable(p[-1])

    if variable:
        if variable.Dimension > 0 and p[1] == '[':
            operator = p[6]
            op2 = variable.Dimension
            tempOp1 = instructions.popOperand()   #value to be assigned.
            op1 = instructions.popOperand()       #index of the array.
            if op1.Type is int:
                instructions.generateQuadruple('VER', op1, op2, variable.Address)
                result = currentDirectory.add_temp(int)
                result.Type = 'arr'
                arrayType.append(variable.Type)
                instructions.generateQuadruple('OFF', variable.Address, op1, result)
                instructions.pushOperand(result)
                op1 = tempOp1
                result = instructions.popOperand()
                if op1.Type is 'arr' and result.Type is 'arr':
                    result.Type = arrayType.pop()
                    op1.Type = arrayType.pop()
                    validType = getResultingType(operator, result.Type, op1.Type)
                    if validType:
                        op1 = '(' + str(op1.Address) + ')'
                        result = '(' + str(result.Address) + ')'
                        operator = 'EQU'
                        instructions.generateQuadruple(operator, op1, 0, result)
                    else:
                        print ("ERROR: Invalid types! Variable '{}' cannot store '{}'!".format(currentDirectory.get_variable(result.Name), currentDirectory.get_variable(op1.Name)))
                        raise SystemExit
                else:
                    result.Type = arrayType.pop()
                    validType = getResultingType(operator, result.Type, op1.Type)
                    if validType:
                        result = '(' + str(result.Address) + ')'
                        operator = 'EQU'
                        instructions.generateQuadruple(operator, op1, 0, result)
                    else:
                        print ("ERROR: Invalid types! Variable '{}' cannot store '{}'!".format(currentDirectory.get_variable(result.Name), currentDirectory.get_variable(op1.Name)))
                        raise SystemExit
            else:
                print("ERROR: Type error. Array index must be of type int, found {} at line {}!".format(op1.Type, p.lineno(1)))
                raise SystemExit
        elif p[1] == '=' and variable.Dimension == 0:
            op1 = instructions.popOperand()
            result = variable
            operator = p[1]

            if op1.Type is 'arr':
                op1.Type = arrayType.pop()
                validType = getResultingType(operator, result.Type, op1.Type)
                if validType:
                    op1 = '(' + str(op1.Address) + ')'
                    operator = 'EQU'
                    instructions.generateQuadruple(operator, op1, 0, result)
                else:
                    print ("ERROR: Invalid types! Variable '{}' cannot store '{}'!".format(currentDirectory.get_variable(result.Name), currentDirectory.get_variable(op1.Name)))
                    raise SystemExit
            else:
                validType = getResultingType(operator, result.Type, op1.Type)
                if validType:
                    operator = 'EQU'
                    instructions.generateQuadruple(operator, op1, 0, result)
                else:
                    print ("ERROR: Invalid types! Variable '{}' cannot store '{}'!".format(currentDirectory.get_variable(result.Name), currentDirectory.get_variable(op1.Name)))
                    raise SystemExit
        else:
            if p[1] == '=':
                print("ERROR: Variable '{}' is of type Array, must be accessed through an index, line {}!".format(p[-1], p.lineno(1)))
                raise SystemExit
            elif p[1] == '[':
                print("ERROR: Variable '{}' is not of type Array, line {}!".format(p[-1], p.lineno(1)))
                raise SystemExit
    else:
        print("ERROR: Variable '{}' not declared in this scope, line {}!".format(p[-1], p.lineno(1)))
        raise SystemExit

def p_superexpression(p):
    '''superexpression : expression superexpression2'''

def p_superexpression2(p):
    '''superexpression2 : andor superexpression
                        | empty'''

def p_andor(p):
    '''andor : AND
             | OR'''
    global instructions
    instructions.pushOperator(p[1])

def p_expression(p):
    '''expression : numeric_expression compare'''
    global instructions, currentDirectory
    if instructions.topOperatorEquals('&&') or instructions.topOperatorEquals('||') :
        op2 = instructions.popOperand()
        op1 = instructions.popOperand()
        operator = instructions.popOperator()

        resultingType = getResultingType(operator, op1.Type, op2.Type)
        if resultingType is None:
            print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
            raise SystemExit

        result = currentDirectory.add_temp(resultingType)

        if result:
            operator = {"&&" : "AND", "||" : "ORR"}[operator]
            instructions.generateQuadruple(operator, op1, op2, result)
            instructions.pushOperand(result)
        else:
            print ("EXPRESSION ERROR")

def p_compare(p):
    '''compare : compare_symbols numeric_expression seen_comp
               | empty'''

def p_compare_symbols(p):
    '''compare_symbols : CEQ
                       | CGT
                       | CGE
                       | CLT
                       | CLE
                       | CNE'''
    global instructions
    instructions.pushOperator(p[1])

def p_seen_comp(p):
    '''seen_comp :'''
    global instructions
    op2 = instructions.popOperand()
    op1 = instructions.popOperand()
    operator = instructions.popOperator()

    if op1.Type is 'arr' and op2.Type is 'arr':
        op2.Type = arrayType.pop()
        op1.Type = arrayType.pop()
        resultingType = getResultingType(operator, op1.Type, op2.Type)
        if resultingType is None:
            print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
            raise SystemExit

        result = currentDirectory.add_temp(resultingType)
        op1 = '(' + str(op1.Address) + ')'
        op2 = '(' + str(op2.Address) + ')'
    elif op1.Type is 'arr':
        op1.Type = arrayType.pop()
        resultingType = getResultingType(operator, op1.Type, op2.Type)
        if resultingType is None:
            print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
            raise SystemExit

        result = currentDirectory.add_temp(resultingType)
        op1 = '(' + str(op1.Address) + ')'
    elif op2.Type is 'arr':
        op2.Type = arrayType.pop()
        resultingType = getResultingType(operator, op1.Type, op2.Type)
        if resultingType is None:
            print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
            raise SystemExit

        result = currentDirectory.add_temp(resultingType)
        op2 = '(' + str(op2.Address) + ')'
    else:
        resultingType = getResultingType(operator, op1.Type, op2.Type)
        if resultingType is None:
            print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
            raise SystemExit

        result = currentDirectory.add_temp(resultingType)

    if result:
        operator = {"==" : "CEQ",
                    "!=" : "CNE",
                    "<"  : "CLT",
                    ">"  : "CGT",
                    "<=" : "CLE",
                    ">=" : "CGE"}[operator]
        instructions.generateQuadruple(operator, op1, op2, result)
        instructions.pushOperand(result)
    else:
        print ("SEEN_COMPARISON ERROR")

def p_numeric_expression(p):
    '''numeric_expression  : term seen_term numeric_expression2'''

def p_seen_term(p):
    '''seen_term :'''
    global instructions
    if instructions.topOperatorEquals('+') or instructions.topOperatorEquals('-'):
        op2 = instructions.popOperand()
        op1 = instructions.popOperand()
        operator = instructions.popOperator()
        if op1.Type is 'arr' and op2.Type is 'arr':
            op2.Type = arrayType.pop()
            op1.Type = arrayType.pop()
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)
            op1 = '(' + str(op1.Address) + ')'
            op2 = '(' + str(op2.Address) + ')'
        elif op1.Type is 'arr':
            op1.Type = arrayType.pop()
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)
            op1 = '(' + str(op1.Address) + ')'
        elif op2.Type is 'arr':
            op2.Type = arrayType.pop()
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)
            op2 = '(' + str(op2.Address) + ')'
        else:
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)

        if result:
            if operator is '+':
                operator = 'ADD'
            elif operator is '-':
                operator = 'SUB'
            instructions.generateQuadruple(operator, op1, op2, result)
            instructions.pushOperand(result)
        else:
            print ("SEEN_TERM ERROR")

def p_numeric_expression2(p):
    '''numeric_expression2 : arithOp numeric_expression
                           | empty'''

def p_arithOp(p):
    '''arithOp : '+'
               | '-' '''
    global instructions
    instructions.pushOperator(p[1])

def p_term(p):
    '''term  : factor term2'''

def p_term2(p):
    '''term2 : geomOp term
             | empty'''

def p_geomOp(p):
    '''geomOp : '*'
              | '/' '''
    global instructions
    instructions.pushOperator(p[1])

def p_factor(p):
    '''factor  : cst_expression
               | '(' seen_lpar superexpression ')' seen_rpar '''
    global instructions
    if instructions.topOperatorEquals('*') or instructions.topOperatorEquals('/'):
        op2 = instructions.popOperand()
        op1 = instructions.popOperand()
        operator = instructions.popOperator()

        if op1.Type is 'arr' and op2.Type is 'arr':
            op2.Type = arrayType.pop()
            op1.Type = arrayType.pop()
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)
            op1 = '(' + str(op1.Address) + ')'
            op2 = '(' + str(op2.Address) + ')'
        elif op1.Type is 'arr':
            op1.Type = arrayType.pop()
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)
            op1 = '(' + str(op1.Address) + ')'
        elif op2.Type is 'arr':
            op2.Type = arrayType.pop()
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)
            op2 = '(' + str(op2.Address) + ')'
        else:
            resultingType = getResultingType(operator, op1.Type, op2.Type)
            if resultingType is None:
                print ("ERROR: Operation '{}' between '{}' and '{}' cannot be performed. Incompatible types!".format(operator, op1.Type, op2.Type))
                raise SystemExit

            result = currentDirectory.add_temp(resultingType)

        if result:
            if operator is '*':
                operator = 'MUL'
            elif operator is '/':
                operator = 'DIV'
            instructions.generateQuadruple(operator, op1, op2, result)
            instructions.pushOperand(result)
        else:
            print ("FACTOR ERROR")

def p_seen_lpar(p):
    '''seen_lpar :'''
    global instructions
    instructions.pushOperator(p[-1])

def p_seen_rpar(p):
    '''seen_rpar :'''
    global instructions
    instructions.popOperator()

def p_cst_expression(p):
    '''cst_expression  : CSTDOUBLE
                       | CSTINT
                       | CSTSTRING
                       | ID seen_id_or_func cst_expression2'''
    global instructions, currentDirectory
    op1 = None
    if type(p[1]) is str:
        if p[1][0] == "\"":
            op1 = currentDirectory.add_const(str, p[1])
    else:
        varType = type(p[1])
        op1 = currentDirectory.add_const(varType, p[1])
    if op1:
        instructions.pushOperand(op1)

def p_seen_id_or_func(p):
    '''seen_id_or_func :'''
    global Function_Or_Var_Name
    Function_Or_Var_Name.append(p[-1])

def p_cst_expression2(p):
    '''cst_expression2 : LSQUARE seen_LSQUARE superexpression RSQUARE seen_RSQUARE
                       | function_call
                       | empty'''
    global instructions, arrayType
    name = Function_Or_Var_Name[len(Function_Or_Var_Name)-1]
    if p[1] and p[1] != "[":
        variable = currentDirectory.get_directory(name).getReturnVariable()
    else:
        variable = currentDirectory.get_variable(name)

    Function_Or_Var_Name.pop()

    if p[1] == "[" and variable:
        op2 = variable.Dimension
        op1 = instructions.popOperand()
        if op1.Type is int:
            instructions.generateQuadruple('VER', op1, op2, variable.Address)
            result = currentDirectory.add_temp(int)
            result.Type = 'arr'
            arrayType.append(variable.Type)
            instructions.generateQuadruple('OFF', variable.Address, op1, result)
            instructions.pushOperand(result)
        else:
            print("ERROR: Type error. Array index must be of type int, found {} at line {}!".format(op1.Type, p.lineno(1)))
            raise SystemExit
    elif variable:
        instructions.pushOperand(variable)
    else:
        print("ERROR: Variable '{}' not declared in this scope!".format(name))
        raise SystemExit

def p_seen_LSQUARE(p):
    '''seen_LSQUARE : '''
    global instructions
    instructions.pushOperator('(')

def p_seen_RSQUARE(p):
    '''seen_RSQUARE : '''
    global instructions
    instructions.popOperator()

def p_function_call(p):
    '''function_call :  seen_func_id '(' args ')' '''
    global instruction, currentDirectory, functionDirectory, parameterCounter
    if len(functionDirectory.parameters) == parameterCounter:
        instructions.generateQuadruple('CAL',0,0,functionDirectory.startAddress)
    else:
        print ("ERROR: Function '{}' received {} arguments, expected {} in line {}!".format(functionDirectory.identifier, parameterCounter,len(functionDirectory.parameters), p.lineno(2)))
        raise SystemExit
    instructions.operatorStack.pop()
    #Function_Or_Var_Name.pop()
    p[0] = True

def p_seen_func_id(p):
    '''seen_func_id : '''
    global currentDirectory, functionDirectory, parameterCounter, instructions, parameterCounterInt, parameterCounterDou, parameterCounterStr
    name = Function_Or_Var_Name[len(Function_Or_Var_Name)-1]
    #Function call as a statement
    functionDirectory = currentDirectory.get_directory(name)

    if not functionDirectory:
        print ("ERROR: Undeclared function: '{}'!".format(name))
        raise SystemExit

    parameterCounter = 0
    parameterCounterInt = 0
    parameterCounterDou = 0
    parameterCounterStr = 0
    instructions.operatorStack.append("(")
    instructions.generateQuadruple("ERA", 7050, 0, 0);

def p_args(p):
    '''args : arg
            | empty'''

def p_arg(p):
    '''arg : numeric_expression seen_arg more_args'''

def p_seen_arg(p):
    '''seen_arg : '''
    global instructions,currentDirectory,functionDirectory,parameterCounter, parameterCounterInt, parameterCounterDou, parameterCounterStr
    op1 = instructions.popOperand()

    if parameterCounter < len(functionDirectory.parameters):
        nextParam = functionDirectory.parameters[parameterCounter]
        variable = functionDirectory.get_variable(nextParam)
        compatibleType = getResultingType('=', variable.Type, op1.Type)
        if variable.Type is compatibleType:
            if variable.Type is int:
                instructions.generateQuadruple('EQU', op1, 0, Variable('Param{}'.format(parameterCounter), variable.Type, parameterCounterInt + 10000))
                parameterCounterInt += 1
            elif variable.Type is float:
                instructions.generateQuadruple('EQU', op1, 0, Variable('Param{}'.format(parameterCounter), variable.Type, parameterCounterDou + 11000))
                parameterCounterDou += 1
            elif variable.Type is str:
                instructions.generateQuadruple('EQU', op1, 0, Variable('Param{}'.format(parameterCounter), variable.Type, parameterCounterStr + 12000))
                parameterCounterStr += 1
        else:
            print ("ERROR: Incompatible arguments. Received '{}', expected '{}'!".format(op1.Type,functionDirectory.get_variable(nextParam).Type))
            raise SystemExit
        parameterCounter += 1
    else:
        print ("ERROR: Function '{}' received more arguments than expected!".format(functionDirectory.identifier))
        raise SystemExit

def p_more_args(p):
    '''more_args : COMMA arg
                | empty'''

def p_loop_statement(p):
    '''loop_statement : loophead block_statement seen_loop_block'''
    global instructions
    pendingJump = instructions.popJumpStack()
    instructions.setQuadrupleResult(pendingJump, instructions.nextInstruction)

def p_loophead(p):
    '''loophead : FOR '(' variable_assignment seen_assignment1 SEMICOLON superexpression seen_for_exp SEMICOLON variable_assignment seen_assignment2 ')'
                | WHILE '(' seen_while_LPAR superexpression seen_while_exp ')' '''

def p_seen_assignment1(p):
    '''seen_assignment1 : '''
    global instructions
    instructions.pushJumpStack(instructions.nextInstruction)

def p_seen_for_exp(p):
    '''seen_for_exp : '''
    global instructions
    condition = instructions.popOperand()

    if condition.Type is bool:
        pendingJump = instructions.popJumpStack()
        instructions.generateQuadruple('GTF', condition, 0, 0)
        instructions.pushJumpStack(instructions.nextInstruction - 1)
        #Pending: exit jump address

        instructions.generateQuadruple('GTO', 0, 0, 0)
        instructions.pushJumpStack(instructions.nextInstruction)
        instructions.pushJumpStack(instructions.nextInstruction - 1)
        #Pending: loop start jump address

        instructions.pushJumpStack(pendingJump)
    else:
        print ("ERROR: Expected type bool, but found {} in FOR Loop!".format(condition.Type))
        raise SystemExit

def p_seen_assignment2(p):
    '''seen_assignment2 : '''
    global instructions
    pendingJump = instructions.popJumpStack()
    instructions.generateQuadruple('GTO', 0, 0, pendingJump)
    #After assigning, jump to condition evaluation

    pendingJump = instructions.popJumpStack()
    instructions.setQuadrupleResult(pendingJump, instructions.nextInstruction)
    #Loop start jump address is right after assigning, where the loop header ends.

def p_seen_while_LPAR(p):
    '''seen_while_LPAR : '''
    global instructions
    instructions.pushJumpStack(instructions.nextInstruction)

def p_seen_while_exp(p):
    '''seen_while_exp : '''
    global instructions
    condition = instructions.popOperand()

    if condition.Type is bool:
        pendingJump = instructions.popJumpStack()
        instructions.generateQuadruple('GTF', condition, 0, 0)
        instructions.pushJumpStack(instructions.nextInstruction - 1)
        instructions.pushJumpStack(pendingJump)
    else:
        print ("ERROR: Expected type bool, but found {} in WHILE Loop!".format(condition.Type))
        raise SystemExit

def p_seen_loop_block(p):
    '''seen_loop_block  :'''
    global instructions
    pendingJump = instructions.popJumpStack()
    instructions.generateQuadruple("GTO", 0, 0, pendingJump)

def p_if_statement(p):
    '''if_statement  : IF '(' superexpression ')' seen_condition block_statement seen_condition_block if_statement2'''
    global instructions
    pendingJump = instructions.popJumpStack()
    instructions.setQuadrupleResult(pendingJump, instructions.nextInstruction)

def p_seen_condition(p):
    '''seen_condition   :'''
    global instructions
    condition = instructions.popOperand()

    if condition.Type is bool:
        instructions.generateQuadruple("GTF", condition, 0, 0)
        instructions.pushJumpStack(instructions.nextInstruction - 1)
    else:
        print ("ERROR: Expected conditional, but found {} in IF Statement!".format(condition.Type))
        raise SystemExit

def p_seen_condition_block(p):
    '''seen_condition_block :'''
    global instructions
    pendingJump = instructions.popJumpStack()
    instructions.generateQuadruple("GTO", 0, 0, 0)
    instructions.pushJumpStack(instructions.nextInstruction -1)
    instructions.setQuadrupleResult(pendingJump, instructions.nextInstruction)

def p_if_statement2(p):
    '''if_statement2 : ELSE block_statement
                     | empty'''

def p_block_statement(p):
    '''block_statement  : LBRACKET block_statement2 RBRACKET
       block_statement2 : statement block_statement2
                        | empty'''

def p_read_statement(p):
    '''read_statement : SCAN '(' ID ')' SEMICOLON'''
    global instructions
    name = p[3]
    variable = currentDirectory.get_variable(name);
    if variable:
        if variable.Type is int:
            instructions.generateQuadruple('RED', variable, 1, 0);
        elif variable.Type is float:
            instructions.generateQuadruple('RED', variable, 2, 0);
        elif variable.Type is str:
            instructions.generateQuadruple('RED', variable, 3, 0);
    else:
        print ("ERROR: Variable '{}' not declared in this scope, line {}!".format(name, p.lineno(1)))
        raise SystemExit

def p_print_statement(p):
    '''print_statement  : PRINT '(' superexpression ')' SEMICOLON'''
    global instructions
    op1 = instructions.popOperand()
    if op1.Type is 'arr':
        op1.Type = arrayType.pop()
        op1 = '(' + str(op1.Address) + ')'
    instructions.generateQuadruple('PRT', op1, 0, 0);

def p_geometry_statement(p):
    '''geometry_statement : move
                          | rotate
                          | arc
                          | home
                          | pdown
                          | pup
                          | setp
                          | pcolor
                          | psize
                          | pclear '''

def p_move(p):
    '''move : MOVE '(' superexpression ')' '''
    global instructions
    op1 = instructions.popOperand()
    if op1.Type is int or op1.Type is float:
        instructions.generateQuadruple('MVT', op1, 0, 0)
    else:
        lineNum = p.lineno(1)
        print ("ERROR: Expected type int or double, but found {} in line!".format(op1.Type, lineNum))
        raise SystemExit

def p_rotate(p):
    '''rotate : ROTATE '(' superexpression ')' '''
    global instructions
    op1 = instructions.popOperand()

    if op1.Type is int or op1.Type is float:
        instructions.generateQuadruple('ROT', op1, 0, 0)
    else:
        lineNum = p.lineno(1)
        print ("ERROR: Expected type int or double, but found {} in line!".format(op1.Type, lineNum))
        raise SystemExit

def p_arc(p):
    '''arc : ARC '(' superexpression COMMA superexpression ')' '''
    global instructions
    op2 = instructions.popOperand()
    op1 = instructions.popOperand()

    if op1.Type is int and op2.Type is int:
        instructions.generateQuadruple('ARC', op1, op2, 0)
    else:
        lineNum = p.lineno(1)
        print ("ERROR: Expected type int, but found {} and {} in line {}!".format(op1.Type, op2.Type, lineNum))
        raise SystemExit

def p_home(p):
    '''home : HOME '(' ')' '''
    global instructions
    instructions.generateQuadruple('HOM', 0, 0, 0)

def p_pdown(p):
    '''pdown : PDOWN '(' ')' '''
    global instructions
    instructions.generateQuadruple('PDO', 0, 0, 0)

def p_pup(p):
    '''pup : PUP '(' ')' '''
    global instructions
    instructions.generateQuadruple('PUP', 0, 0, 0)

def p_setp(p):
    '''setp : SETP '(' superexpression COMMA superexpression ')' '''
    global instructions
    op2 = instructions.popOperand()
    op1 = instructions.popOperand()

    if op1.Type is int and op2.Type is int or op1.Type is int and op2.Type is float or op1.Type is float and op2.Type is int or op1.Type is float and op2.Type is float:
        instructions.generateQuadruple('SET', op1, op2, 0)
    else:
        lineNum = p.lineno(1)
        print ("ERROR: Expected type int or double, but found {} and {} in line{}!".format(op1.Type, op2.Type, lineNum))
        raise SystemExit

def p_pcolor(p):
    '''pcolor : PCOLOR '(' superexpression COMMA superexpression COMMA superexpression ')' '''
    global instructions
    op3 = instructions.popOperand()
    op2 = instructions.popOperand()
    op1 = instructions.popOperand()
    if op1.Type is int and op2.Type is int and op3.Type is int:
        instructions.generateQuadruple('PCO', op1, op2, op3)
    else:
        lineNum = p.lineno(1)
        print ("ERROR: Expected type int, but found {}, {} and {} in line{}!".format(op1.Type, op2.Type, op3.Type, lineNum))
        raise SystemExit

def p_psize(p):
    '''psize : PSIZE '(' superexpression ')' '''
    global instructions
    op1 = instructions.popOperand()

    if op1.Type is int:
        instructions.generateQuadruple('PSZ', op1, 0, 0)
    else:
        lineNum = p.lineno(1)
        print ("ERROR: Expected type int, but found {} in line{}!".format(op1.Type, lineNum))
        raise SystemExit

def p_pclear(p):
    '''pclear : PCLEAR '(' ')' '''
    global instructions
    instructions.generateQuadruple('PCL', 0, 0, 0)

def p_statement(p):
    '''statement : variable_declaration
                 | variable_assignment SEMICOLON
                 | superexpression SEMICOLON
                 | if_statement
                 | loop_statement
                 | return
                 | print_statement
                 | read_statement
                 | geometry_statement SEMICOLON'''

def p_return(p):
    '''return : RETURN superexpression SEMICOLON'''
    global currentDirectory, instructions
    if currentDirectory.Type is not 'void':
        var = instructions.popOperand()
        compatibleType = getResultingType('=', currentDirectory.Type, var.Type)

        if compatibleType:
            instructions.generateQuadruple('RET',var,0,0)
        else:
            print ("ERROR: Incompatible return types! Received {}, expected {}!".format(var.Type, currentDirectory.Type))
            raise SystemExit

def p_empty(p):
    'empty :'
    pass

def p_error(p):
   if p:
    if p.value in reserved:
        print("ERROR: Syntax error at '{}' in line {}. Cannot use reserved word '{}' as a variable or function!".format(p.value, p.lineno, p.value))
    else:
        print("ERROR: Syntax error at '{}' in line {}!".format(p.value, p.lineno))
    raise SystemExit

#Test routine
if __name__ == '__main__':
    if len(sys.argv) == 2:
        f = open(sys.argv[1], 'r')
        s = f.read()
        parser = yacc.yacc()
        parser.parse(s);
        # print (currentDirectory.parent)
        # print (instructions)

        myFile = open("cheeseCode.rekt", 'w')
        myFile.write(str(currentDirectory.parent.getConstantDeclarations()))
        myFile.write(str(instructions))
        myFile.close()
    else:
        print ("Usage syntax: %s filename" %sys.argv[0])


"""
FA_Server will be updated to that in 'server_FAServer_bit.py' in the future.
"""
from math import ceil, log
import random, sys, json
from typing import Dict, List
from joblib import Parallel, delayed
from tqdm import tqdm
from tqdm.contrib import DummyTqdmFile
import contextlib
from privacy_module import PrivacyModule
from evaluate_module import EvaluateModule
from server import BaseServer
from utils import distance, load_clients, blockPrint, enablePrint

random.seed(0)
# blockPrint()

class FAserver(BaseServer):
    def __init__(self, n: int, m: int, k: int, varepsilon: float, iterations: int, round: int,
        clients: List = [], C_truth: List = [], privacy_mechanism_type: List = "GRR_X", evaluate_type: str = "F1",
        connection_loss_rate: float = 0, is_uniform_size: bool=False, stop_iter = -1):
            super().__init__(n, m, k, varepsilon, iterations, round, clients, C_truth, privacy_mechanism_type, evaluate_type, connection_loss_rate)
            self.bits_per_iter =ceil(self.m / self.iterations)
            # self.trie = TrieNumeric(self.bits_per_iter, k = k)
            self.is_uniform_size = is_uniform_size
            if stop_iter == -1:
                self.stop_iter = iterations
            else:
                self.stop_iter = stop_iter
    def predict_heavy_hitters(self) -> Dict:
        """_summary_

        Args:
            n (int): client size
            m (int): binary-string length
            k (int): top-k heavy hitters
            varepsilon (float): privacy budget
            iterations (int): number of groups
        Returns:
            Dict: top-k heavy hitters C_g and their frequencies.
        """

        s_0 = 0
        A_i = {}
        A_i[0] = 0 # initial weight_score
        participants = 0
        bits_per_batch = ceil(self.m / self.iterations)

        for i in range(self.stop_iter):
            s_i = min(s_0 + bits_per_batch, self.m) # current bit length
            delta_s = s_i - s_0 # required bit length, e.g. 2bits per iter.
            s_0 = s_i # last iter's bit length

            clients_responses = []

            if self.is_uniform_size : adder = int(self.n/self.stop_iter)

            else: adder = int(self.n /(2*self.stop_iter) + (i) * self.n / (self.stop_iter* (self.stop_iter + 1)))
            print(f"Sampling {adder} clients")
            end_participants = participants + adder

            if i == self.stop_iter-1:
                end_participants = self.n-1
            for client_id in range(participants, end_participants+1):
                client_v = self.clients[client_id]
                privacy_module = PrivacyModule(self.varepsilon, A_i, type=self.privacy_mechanism_type,s_i = s_i, required_bits = delta_s, client_id=client_id)
                mechanism = privacy_module.privacy_mechanism()

                if self.privacy_mechanism_type=="GRR_X" and privacy_module.p <= 0.5:
                    privacy_module = PrivacyModule(self.varepsilon, A_i, type="GRR",s_i = s_i, required_bits = delta_s, client_id=client_id)
                    mechanism = privacy_module.privacy_mechanism()

                handle_response = privacy_module.handle_response()

                prefix_client = client_v >> (self.m-s_i)
                response = mechanism(prefix_client)
                # simulate connection loss rate
                p = random.random()
                if p >= self.connection_loss_rate and response is not None:
                    clients_responses.append(response)
            participants = end_participants

            C_i = handle_response(clients_responses)

            C_i_sorted = sorted(C_i.items(), key=lambda x: x[-1], reverse=True)

            A_i = {}
            # a  = self.k*2**self.bits_per_iter if i==self.stop_iter else self.k
            a = len(C_i_sorted) if i==self.stop_iter-1 else self.k
            for indx in range(min(a, len(C_i_sorted))):
                v, count = C_i_sorted[indx]
                if count > 0:
                    A_i[v] = 0  # validate v in next iteration
                    if i == self.stop_iter-1: A_i[v] = count
            if not A_i:
                A_i = {0: 0}
        self.accurate_bits = self.m-s_0
        if self.accurate_bits:
            print(f"accurate bits: {self.accurate_bits}")
            A_tmp = A_i
            A_i = {}
            for i in A_tmp.keys():
                # change key  A_i[i << self.accurate_bits] = A_i[i]
                A_i[i << self.accurate_bits] = A_tmp[i]

        return A_i

def FA_cluster(clients: list, k: int, evaluate_module_type="recall", m=64, iterations=32, varepsilon=2, connection_loss_rate=0, stop_iter=-1):
    """_summary_

    Args:
        clients (list): clients' data
        k (int): top k
        evaluate_module_type (str, optional): performace evaluation "recall", "F1" . Defaults to "recall".
        m (int, optional): maximum length of binary strings. Defaults to 64.
        iterations (int, optional): number of iterations/trie's height. Defaults to 32.
        varepsilon (float, optional): LDP privacy parameter. Defaults to 2.
    return:
        str: json string of one cluster's result
    """
    # encode clients into bitstrings
    round = 1

    n = len(clients)
    # print("[debug]:: n", n)
    server = FAserver(n, m, k, varepsilon, iterations, round, clients=clients, C_truth=None,
                         evaluate_type=evaluate_module_type, connection_loss_rate=connection_loss_rate,
                         is_uniform_size=False,
                            stop_iter=stop_iter
                         )

    x_xtf, _ = server.server_run()

    predict_hh = server.A_i

    results = {'predict_hh': predict_hh,  'eps': x_xtf}
    return json.dumps(results)

@contextlib.contextmanager
def std_out_err_redirect_tqdm():
    orig_out_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err)
        yield orig_out_err[0]
    # Relay exceptions
    except Exception as exc:
        raise exc
    # Always restore sys.stdout/err if necessary
    finally:
        sys.stdout, sys.stderr = orig_out_err

def FA_aggregation(clients: list, k: int, global_truth_top_k: list, saved_csv_local_global, varepsilon: float = 2, evaluate_type="recall", m=64, iterations=32):
    """_summary_

    Args:
        n (int): number of clients
        clients (list[list]): list of clusters of clients
        k (int): top k
        global_truth_top_k (list): global truth top k
        var_epsilon (float, optional): LDP privacy parameter. Defaults to 2.
        evaluate_type (str, optional): performace evaluation "recall", "F1" . Defaults to "recall".
        cluster_size (int, optional): number of clients in one cluster . Defaults to 5000.
        m (int, optional): maximum length of binary strings. Defaults to 64.
        iterations (int, optional): number of iterations/trie's height. Defaults to 32.

    Returns:
        float: score (F1 or recall)
    """

    clusters = len(clients)
    total_clients = sum(len(clients[i]) for i in range(clusters))
    blockPrint()
    # Redirect stdout to tqdm.write() (don't forget the `as save_stdout`)
    with std_out_err_redirect_tqdm() as orig_stdout:
        # tqdm needs the original stdout
        # and dynamic_ncols=True to autodetect console width
        results = Parallel(n_jobs=clusters)(delayed(FA_cluster)(
            clients[i], k, evaluate_type, m, iterations, varepsilon) for i in tqdm(range(clusters)))
    enablePrint()
    candidates_among_clusters = {}
    tmp_csv_row = []
    for i in range(len(results)):
        result_i = json.loads(results[i])
        predict_hh = result_i['predict_hh']
        tmp_csv_row.append(f"{list(predict_hh.keys())[:k]}")
        for hh in predict_hh:
            x = predict_hh[hh]
            if x <= k:
                continue
            incre = x/sum(predict_hh.values())
            candidates_among_clusters[hh] = candidates_among_clusters.get(hh, 0) + incre

        # aggregate results
    candidates_among_clusters = sorted(candidates_among_clusters.items(),
                           key=lambda x: x[1], reverse=True)

    candidates_n = len(candidates_among_clusters)

    good_hhs = []
    bad_hh = []
    for i in range(candidates_n):
        x = int(candidates_among_clusters[i][0])
        if x in bad_hh:
            continue
        for j in range(i+1, candidates_n):

            y = int(candidates_among_clusters[j][0])
            threshold = (min(len(bin(x)), len(bin(y))) - 2)/2
            dis = distance(x, y)
            if dis <= threshold and candidates_n - len(bad_hh) > k:
                bad_hh.append(y)
                continue
            # print(f"{x} {y}: distance = {dis}")
        good_hhs.append(x)
        if len(good_hhs) == k: break
    print("global truth_top_k: ", global_truth_top_k)
    evaluate_module = EvaluateModule(evaluate_type)
    score = evaluate_module.evaluate(global_truth_top_k, good_hhs)
    print("finall hhs: ", good_hhs, " score:", score)

    print("Done!")
    tmp_csv_row.append(f"{good_hhs}")
    tmp_csv_row.append(f"{global_truth_top_k}")

    saved_csv_local_global.append(tmp_csv_row)
    return score, saved_csv_local_global

def FA_running_rounds(n: int, clients: list, k: int, global_truth_top_k: list, varepsilon: float = 2, step_varepsilon=0.4, max_varepsilon=2.2,
                         evaluate_type="recall", cluster_size=5000, m=64, iterations=32, rounds=10):
    """_summary_

    Args:
        n (int): number of clients
        clients (list): clients' data
        k (int): top k
        global_truth_top_k (list): global truth top k
        varepsilon (float, optional): LDP privacy parameter. Defaults to 2.
        step_varepsilon (float, optional): step of varepsilon. Defaults to 0.4.
        max_epsilon (float, optional): max varepsilon. Defaults to 2.2.
        cluster_size (int, optional): number of clients in one cluster . Defaults to 5000.
        m (int, optional): maximum length of binary strings. Defaults to 64.
        iterations (int, optional): number of iterations/trie's height. Defaults to 32.
        rounds (int, optional): number of rounds. Defaults to 10.

    Returns:
        list[float], list[float]: epsilons, scores
    """
    varepsilons = []
    scores = []
    while varepsilon < max_varepsilon:
        # running rounds
        score = 0
        for i in range(rounds):
            score += FA_aggregation(n, clients, k, global_truth_top_k, varepsilon, evaluate_type, cluster_size, m, iterations)

        varepsilons.append(varepsilon)
        scores.append(score/rounds)
        varepsilon += step_varepsilon

    # average score
    return varepsilons, scores


from typing import List
from functools import cmp_to_key

def compareVersion(version1: str, version2: str) -> int:
    nums1, nums2 = version1.split("."), version2.split(".")
    len1, len2 = len(nums1), len(nums2)

    #        i
    # [1, 0]
    # [1, 0, 0]
    for i in range(max(len1, len2)):
        num1 = int(nums1[i]) if i < len1 else 0
        num2 = int(nums2[i]) if i < len2 else 0
        if num1 < num2:
            return -1
        elif num1 > num2:
            return 1
    return 0

def compareVersionBranch(version1: str, version2: str) -> int:
    strs1 = version1.split("-")
    strs2 = version2.split("-")
    v1, v2 = strs1[0], strs2[0]
    br1 = strs1[1] if len(strs1) > 1 else ""
    br2 = strs2[1] if len(strs2) > 1 else ""

    version_compare = compareVersion(v1, v2)
    if version_compare != 0:
        return version_compare
    # == 0
    if br1 == br2:
        return 0
    return 1 if br1 > br2 else -1

versions = ["1.1", "1.2.2", "1.1.2", "1.2"]
stages = ["1.1-dev", "1.1-rc2", "1.1.2-rc1", "1.2.2", "1.2-dev", "1.2-prod"]

#print(compareVersionBranch("1.1.2-rc1", "1.1.2-rc2"))
#print(sorted(versions, key=cmp_to_key(compareVersion)))
print(sorted(stages, key=cmp_to_key(compareVersionBranch)))

nums = [1, 4, 2, 5]
print(list(map(str, nums)))


# Only works in Python 3.6.7 32 Bit. Navigate to the directory of Python 3.6.7 and into the Scripts folder. Open a command prompt in that directory and run 'pip3.6.exe install pypyodbc' without the quotes. The program should then work from a Python 3.6.7 32 bit interpreter.
# This program was written in December 2018 by RevolverDrummer
# The only library that needs to be installed is pypyodbc. Once this is on a the system, this program will work.

import random
import pypyodbc
import os.path

# Get the absolute path of the working environment for the eventual SQL connection.
pathVar = os.path.abspath("")
dbPathVar = os.path.abspath("es_names_real.mdb")

# Flip the backslashes to forward slashes in the abs path.
for i in pathVar:
    pathVar = pathVar.replace("\\","/")

# Flip the backslashes to forward slashes in the abs path of the database.
for i in dbPathVar:
    dbPathVar = dbPathVar.replace("\\","/")

# Main Menu for greeting the user and asking for the race of their character.
def main_menu():
    """Returns the corresponding number of the race that the user selected

    :param raceNum (string) the number the user selects for the corresponding race
    :return raceNum (string)
    """
    print(" _________________________________________________")
    print("/\\                                                \\")
    print("\_|                                                |")
    print("  |  Welcome to the Elder Scrolls Name Generator!  |")
    print("  |  Please select a race for your character:      |")
    print("  |                                                |")
    print("  |      1. Altmer           2. Argonian           |")
    print("  |      3. Bosmer           4. Breton             |")
    print("  |      5. Dunmer           6. Imperial           |")
    print("  |      7. Khajiit          8. Nord               |")
    print("  |      9. Orsimer          10. Redguard          |")
    print("  |                                                |")
    print("  |  ______________________________________________|_")
    print("  \_/_______________________________________________/")
    while True:
        # Get user's input for number
        raceNum = input()
        # Try converting raceNum to an int, if that doesn't work then its invalid, so try again.
        try:
            int(raceNum)
            # Check if valid int raceNum is within the boundaries of possible selections.
            if int(raceNum) < 1 or int(raceNum) > 10:
                print("Please input a valid option (Number 1 through 10.)")
            else:
                break
        except:
            print("Please input a valid option (Number 1 through 10.)")
    return raceNum

# Funtion for races with only one name
def first_only(race, gender):
    """Returns a random name for a race with only one name

    :param namelist (list) The Python list that all the names for the selected race and gender. Will be populated from the backend database.
    :param con (string) The main variable set to handle the SQL functions
    :param name (string) The Python Variable to store names as they come in from the SQL database
    :param fstatement (string) The SQL statment to be executed
    :param firstname (string) The variable to represent the selected first name of the random choice
    :param race (string) The race the user inputted
    :param gender (string) The gender the user inputted
    :return name (string) the selected name for the user
    """
    # Altmer, Bosmer, Argonians, and Khajiit do not have traditional surnames.
    namelist = []
    # The connect statement to initialize the Python connection to the SQL server.
    con = pypyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};UID=admin;UserCommitSync=Yes;Threads=3;SafeTransactions=0;PageTimeout=5;MaxScanRows=8;MaxBufferSize=2048;FIL={MS Access};DriverId=25;DefaultDir='+pathVar+';DBQ='+dbPathVar+'')
    # I believe this 'selects' the first area in the SQL database, essentially prepping it for parsing.
    name = con.cursor()
    # Preparing the SQL statement outside of the execute function so that the Python variables can be passed in easily.
    fstatement = "SELECT first FROM Names WHERE race='"+race+"' AND gender='"+gender+"'"
    # Execute the SQL statement
    name.execute(fstatement)
    # Get all the names that match the SQL query and append them to the list within Python.
    for row in name.fetchall():
        namelist.append(str(row))
    # From the newly formed Python list, pick a random name.
    firstname = random.choice(namelist)
    # Normalize (take out the parenthesis and comma) the chose name.
    firstname = firstname[2:-3]
    # Capitalize the race to make the final output pretty.
    race = race.capitalize()
    # Print the final result to the user.
    return firstname

# Function for races with a first and last name
def first_last(race, gender):
    """Returns a random name for a race with a first name and last name

    :param fnamelist (list) The Python list that all the first names for the selected race and gender. Will be populated from the backend database.
    :param lnamelist (list) The Python list that all the last names for the selected race and gender. Will be populated from the backend database.
    :param con (string) The main variable set to handle the SQL functions
    :param fname (string) The Python Variable to store first names as they come in from the SQL database
    :param fstatment (string) The first name SQL statement to be executed
    :param lname (string) The Python Variable to store last names as they come in from the SQL database
    :param lstatement (string) The last name SQL statement to be executed
    :param firstname (string) The variable to represent the selected first name of the selection
    :param lastname (string) The variable to represent the selected last name of the selection
    :param race (string) The race the user inputted
    :return name (string) The selected name for the user
    """
    # Initialize the lists for both first names and last names.
    fnamelist = []
    lnamelist = []
    # The connect statement to initialize the Python connection to the SQL server.
    con = pypyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};UID=admin;UserCommitSync=Yes;Threads=3;SafeTransactions=0;PageTimeout=5;MaxScanRows=8;MaxBufferSize=2048;FIL={MS Access};DriverId=25;DefaultDir='+pathVar+';DBQ='+dbPathVar+'')
    # I believe this 'selects' the first area in the SQL database, essentially prepping it for parsing. Only done for the first name side of things.
    fname = con.cursor()
    # Preparing the SQL statement outside of the execute function so that the Python variables can be passed in easily. This statement only applies to first names.
    fstatement = "SELECT first FROM Names WHERE race='"+race+"' AND gender='"+gender+"'"
    # Execute the SQL statement
    fname.execute(fstatement)
    lname = con.cursor()
    # Preparing the SQL statement outside of the execute function so that the Python variables can be passed in easily. This statement only applies to last names.
    lstatement = "SELECT last FROM Names WHERE race='"+race+"' AND last<>'None'"
    # Execute the SQL statement
    lname.execute(lstatement)
    # Get all the first names that match the SQL query and append them to the first name list within Python.
    for row in fname.fetchall():
        fnamelist.append(str(row))
    # Get all the last names that match the SQL query and append them to the last name list within Python.
    for row in lname.fetchall():
        lnamelist.append(str(row))
    # From the newly formed first name Python list, pick a random name.
    firstname = random.choice(fnamelist)
    # Normalize (take out the parenthesis and comma) the chose name.
    firstname = firstname[2:-3]
    # From the newly formed last name Python list, pick a random name.
    lastname = random.choice(lnamelist)
    # Normalize (take out the parenthesis and comma) the chose name.
    lastname = lastname[2:-3]
    # Capitalize the race to make the final output pretty.
    race = race.capitalize()
    # Print the final result to the user.
    name = firstname+" "+lastname
    return name

#Main
# Variable for repeating loop set to yes initially.
repeater = 'y'
# Sametype set to no so user can set initial values desired.
sametype = 'n'
race = ""
# Start the main loop.
while repeater == 'y':
    race = race.lower()
    # If the user does not want the same type (or is the first time) get their desired race and gender option.
    if sametype != 'y':
        # Call the main menu function to get the race.
        raceNum = main_menu()
        # Set the user's chosen number to the corresponding race.
        if raceNum == "1":
            race = "altmer"
        elif raceNum == "2":
            race = "argonian"
        elif raceNum == "3":
            race = "bosmer"
        elif raceNum == "4":
            race = "breton"
        elif raceNum == "5":
            race = "dunmer"
        elif raceNum == "6":
            race = "imperial"
        elif raceNum == "7":
            race = "khajiit"
        elif raceNum == "8":
            race = "nord"
        elif raceNum == "9":
            race = "orsimer"
        elif raceNum == "10":
            race = "redguard"
        # Ask for the gender of the character.
        while True:
            gender = input('What is the gender of the character you are creating? ')
            if gender == "male" or gender == "female":
                break
            else:
                print("Please input a compatible answer (male/female)")
        # Normalize the input to be lowercase.
        gender = gender.lower()

    # Single name races
    if race == 'altmer' or race == "argonian" or race == "bosmer" or race == "khajiit":
        # Call the first only function and pass in the race and gender variables. Get the name back.
        name = first_only(race, gender)
        # Print the user's generated name
        race = race.capitalize()
        print("Your "+race+"'s random name is "+name+".")
    # Two name races
    elif race == 'breton' or race == "dunmer" or race == "imperial" or race == "nord" or race == "orsimer" or race == "redguard":
        # Call the two name function and pass in the race and gender variables. Get the name back.
        name = first_last(race, gender)
        # Print the user's generated name
        race = race.capitalize()
        print("Your "+race+"'s random name is "+name+".")
    else:
        # If the user inputs something outside expected input then tell them it was invalid.
        print("Invalid Input")
    # Ask the user if they'd like to generate another name.
    repeater = input("Would you like to do another name? (y/n) ")
    # If they want to generate another name ask if they want it to be the same race and gender
    if repeater == 'y':
        sametype = input("Same race and gender? (y/n) ")
    # If they don't say yes to making another name, break the loop
    else:
        break
# Thank the user for their time and wait before exiting the program.
input("Thank you for using the Elder Scrolls Name Generator! [Press Enter to Exit]")


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2017/5/3 下午5:13
# @Author  : mrlittlepig
# @Site    : www.mrlittlepig.xyz
# @File    : itemsCheck.py
# @Software: PyCharm Community Edition

import numpy
import cv2
from PIL import Image

image_dir = './items.jpg'

# 灰度打开
image = Image.open(image_dir).convert('L')
img_ndarray = numpy.asarray(image, dtype='float32')
shape = img_ndarray.shape

# 二值化
for r in range(shape[0]):
    for c in range(shape[1]):
        if img_ndarray[r][c] <= 195:
            img_ndarray[r][c] = 0
        else:
            img_ndarray[r][c] = 255

# 计算连通域
img = cv2.imread(image_dir)
n = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print(n)

tm_img = Image.fromarray(img_ndarray)
Image._show(tm_img)


# ------------------------------------------------------------------------------
# Test ThisEvent
# ------------------------------------------------------------------------------
import sys
import datetime as dt
from django_bs_test import TestCase
from django.contrib.auth.models import User
from wagtail.core.models import Page
from ls.joyous.models import SpecificCalendarPage
from ls.joyous.models import SimpleEventPage, ThisEvent

# ------------------------------------------------------------------------------
class Test(TestCase):
    def setUp(self):
        self.home = Page.objects.get(slug='home')
        self.user = User.objects.create_user('i', 'i@joy.test', 's3cr3t')
        self.calendar = SpecificCalendarPage(owner = self.user,
                                             slug  = "events",
                                             title = "Events")
        self.home.add_child(instance=self.calendar)
        self.calendar.save_revision().publish()
        self.page = SimpleEventPage(owner = self.user,
                                    slug   = "agfest",
                                    title  = "AgFest",
                                    date   = dt.date(2015,6,5),
                                    time_from = dt.time(11),
                                    time_to   = dt.time(17,30))
        self.calendar.add_child(instance=self.page)
        self.page.save_revision().publish()

    def testInit(self):
        event = ThisEvent(self.page, title="Mud mud mud")
        self.assertEqual(event.page, self.page)
        self.assertEqual(event.title, "Mud mud mud")
        self.assertEqual(event.url, "/events/agfest/")

    def testInit2Args(self):
        with self.assertRaises(TypeError):
            event = ThisEvent(self.page, "Mud mud mud")

    def testRepr(self):
        event = ThisEvent(self.page, url="/events/muddy/")
        self.assertEqual(repr(event),
                         "ThisEvent (title='AgFest', "
                         "page=<SimpleEventPage: AgFest>, "
                         "url='/events/muddy/')")

    def testPageAttrs(self):
        event = ThisEvent(self.page)
        self.assertEqual(event.title, "AgFest")
        self.assertEqual(event.slug, "agfest")
        self.assertEqual(event.date, dt.date(2015,6,5))

    def testTheseAttrs(self):
        event = ThisEvent(self.page)
        event.title = "Mud"
        event.url = "/events/muddy/"
        self.assertEqual(event.title, "Mud")
        self.assertEqual(self.page.title, "AgFest")
        self.assertEqual(event.url, "/events/muddy/")
        self.assertEqual(self.page.url, "/events/agfest/")

    def testNewAttrs(self):
        event = ThisEvent(self.page, foo="foo")
        event.bar = "bar"
        self.assertTrue(hasattr(event, "foo"))
        self.assertFalse(hasattr(self.page, "foo"))
        self.assertEqual(event.foo, "foo")
        self.assertTrue(hasattr(event, "bar"))
        self.assertFalse(hasattr(self.page, "bar"))
        self.assertEqual(event.bar, "bar")

# ------------------------------------------------------------------------------
class TestBackCompat(TestCase):
    """
    ThisEvent maintains backwards compatibility with the namedtuple it used to be
    """
    def setUp(self):
        self.home = Page.objects.get(slug='home')
        self.user = User.objects.create_user('i', 'i@joy.test', 's3cr3t')
        self.calendar = SpecificCalendarPage(owner = self.user,
                                             slug  = "events",
                                             title = "Events")
        self.home.add_child(instance=self.calendar)
        self.calendar.save_revision().publish()
        self.page = SimpleEventPage(owner = self.user,
                                    slug   = "agfest",
                                    title  = "AgFest",
                                    date   = dt.date(2015,6,5),
                                    time_from = dt.time(11),
                                    time_to   = dt.time(17,30))
        self.calendar.add_child(instance=self.page)
        self.page.save_revision().publish()

    def testNamedAttrs(self):
        event = ThisEvent("Mud mud mud", self.page, "/events/muddy/")
        self.assertEqual(event.title, "Mud mud mud")
        self.assertEqual(event.page, self.page)
        self.assertEqual(event.url, "/events/muddy/")

    def testExplode(self):
        event = ThisEvent("Mud mud mud", self.page, "/events/muddy/")
        title, page, url = event
        self.assertEqual(title, "Mud mud mud")
        self.assertEqual(page, self.page)
        self.assertEqual(url, "/events/muddy/")

    def testItems(self):
        event = ThisEvent("Mud mud mud", self.page, "/events/muddy/")
        self.assertEqual(len(event), 3)
        self.assertEqual(event[0], "Mud mud mud")
        self.assertEqual(event[1], self.page)
        self.assertEqual(event[2], "/events/muddy/")

    def testSlice(self):
        event = ThisEvent("Mud mud mud", self.page, "/events/muddy/")
        title, page = event[:2]
        self.assertEqual(title, "Mud mud mud")
        self.assertEqual(page, self.page)

    def testAsDict(self):
        event = ThisEvent("Mud mud mud", self.page, "/events/muddy/")
        attrs = event._asdict()
        self.assertEqual(attrs['title'], "Mud mud mud")
        self.assertEqual(attrs['page'], self.page)
        self.assertEqual(attrs['url'], "/events/muddy/")

    def testQuery(self):
        events = list(SimpleEventPage.events.this())
        self.assertEqual(len(events), 1)
        event = events[0]
        self.assertTrue(isinstance(event, ThisEvent))
        self.assertEqual(event.title, "AgFest")
        self.assertEqual(event.page, self.page)
        self.assertEqual(event.url, "/events/agfest/")

# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------


from nltk.corpus import stopwords
from nltk.tokenize import sent_tokenize, word_tokenize
from nltk import pos_tag
from nltk.util import ngrams

class FeatureExtractModule():
	# 你可以將你想要的特徵擷取函式寫在FeatureExtractModule
	# 因此，該模組只適合將建立FeatureExtraction_Func在這
	def __init__(self):
		self.stopword_set = stopwords.words('english')

	def Unigram_FEF(self, tweet):
		# 採用詞性
		pos_candidates = ['NN', 'JJ', 'VB', 'RB']

		# 將Tweet做sentence tokenize
		tweet_sents = sent_tokenize(tweet)

		# 進行word tokenize 以及標註詞性
		word_pos_set = [word_pos for sentence in tweet_sents for word_pos in pos_tag(word_tokenize(sentence))]

		# 將適合的字加入到feature
		feature = [word_pos[0].lower() for word_pos in word_pos_set
									   if word_pos[0].lower() not in self.stopword_set
									   and word_pos[1][0:2] in pos_candidates]

		# 回傳特徵
		return feature


from typing import Optional

from fastapi import FastAPI, Path
from pydantic import BaseModel

app = FastAPI()

students = {
    1: {
        "name": "Nirupam",
        "age": 24,
    },
    2: {
        "name": "John",
        "age": 25,
    },
    3: {
        "name": "Dave",
        "age": 24,
    }
}

class Student(BaseModel):
    name: str
    age: int

# api to get some data in the home route
@app.get("/")
def showdata():
    return {"hello": "hii"}

# api to get some data from an endpoint and path
@app.get("/student_details/{student_id}")
def student_details(student_id: int = Path(None, description="The id of the student to get", gt=0)):
    print(student_id)
    print('......................')
    print(students)
    print('......................')
    return students[student_id]

# api to get some data from an endpoint and query params
@app.get("/student_details_by_name")
# To make a query param optional : queryName :Optional[data type]=None
# Also when we try to put a required param after an optional parameter we get an error
# To overcome that we give a first parameter as '*' so that we can place optional and required parameters in any order
def get_student_by_name(*, student_name: Optional[str] = None, another_param: int):
    for student_id in students:
        if students[student_id]["name"] == student_name:
            return students[student_id]
    return {"data": "No such student exists"}

# Update the data ...Using pydantic for giving a schema like thing in the body
@app.post("/update_student")
def update_student_details(student_id:int, student: Student):
    print(".........data from user .........")
    print(student)
    print(".........data from user .........")
    if student_id in students:
        return {"message": "data already present"}
    students[student_id] = student
    print('......................')
    print(students)
    print('......................')
    return student


import os
import logging as log
import pandas as pd
import re
import csv
from PySheetAdaptor.Drivers.UtilityDriver import DestinationEngineNotAvailableException
## ====================================================================================================================

class BackupDriver:
    """
    Backups the data from the backup file.......
    Backup is the process of creating a copy of the data on your system that you use for recovery in case your original
    data is lost or corrupted.You can also use backup to recover copies of older files if you have deleted them from
    your system....
    """
    def __init__(self,  *args, **kwargs) -> None:
        self.files = kwargs.pop("files", [])
        self.file_name = kwargs.pop("file_name", None)
        self.records = [[],]*int(kwargs.pop("size",2000))
        self.data = ""
        self.dataframe = pd.DataFrame
        self.count = 0
        self.indexes = [None]*int(kwargs.pop("size",2000))
        self.record_lines = []
        self.backed_up_data: str = ""
        self.data_size: int = 0
        self.report: log.Logger = None
        self.consoleFormatter: log.Formatter = None
        self.consoleHandler: log.StreamHandler = None
        self.data_batch_with_dates: dict = dict()
        self.total_transactions = 0
        self.destination_url = None
        self.destination_name = None
        self.set_status_handlers()

    def set_status_handlers(self, *args, **kwargs) -> None:
        self.report = log.getLogger('Report')
        self.report.setLevel(log.DEBUG)
        self.consoleHandler = log.StreamHandler()
        self.consoleHandler.setLevel(log.DEBUG)
        self.consoleFormatter = log.Formatter(f"%(message)s" ,datefmt='%Y-%m-%d => %H:%M ')
        self.consoleHandler.setFormatter(self.consoleFormatter)
        self.report.addHandler(self.consoleHandler)

    def config_data_from_files(self) -> bool:
        if len(self.files)>0:
            for file in self.files:
                if os.path.isfile(file):
                    with open(file, "r") as data:
                        file_data = data.readlines()
                    self.data += file_data
                    self.report.info(f"Data collected from the file :: {file}")
                else:
                    self.report.error(f"""
                    OOPs!!!! NoFilesFound.........
                    couldn't read the data......
                    """)
                    raise FileNotFoundError(f"No file exists with file url :: {file}")
        else:
            if os.path.isfile(self.file_name):
                with open(self.file_name, "r") as data:
                    self.data = data.readlines()
                self.report.info(f"Data collected from the file :: {self.file_name}")
            else:
                self.report.error(f"""
                    OOPs!!!! NoFileFound.........
                    couldn't read the data......
                    """)
                raise FileNotFoundError(f"No file exists with file url :: {self.file_name}")

    def parse_as_logbook(self):
        lines = []
        self.report.info(f"Parsing data as logbook parser....")
        for line in self.data:
            lines.append(line)
            if line[0] == "-":
                self.record_lines.append(lines)
                self.total_transactions += 1
                lines = []
        self.report.info(f"""
            Data parsed successfully....
            file url                 :: {self.file_name}
            Total no of Transactions :: {self.total_transactions}
            """
                         )

    def get_all_logs(self, key):
        if key is None or key == "":
            return [[None, None, None]]
        logs = []
        self.report.info(f"Getting all logs with key = {key}")
        for lines in self.record_lines:
            pattern = re.compile(key)
            matches = pattern.finditer(lines[1])
            for match in matches:
                logs.append(lines)
                break
        return logs

    def print_logs(self, logs):
        for log in logs:
            print(log[0],log[1],log[2],end=" ")

    def parse_command(self, line):
        is_row_level = False
        commands = line.split("-")
        if commands[0] == "Insert":
            record = commands[2].split(":")
            self.records[int(commands[1])] = record
            self.indexes[int(commands[1])] = record[0]
            is_row_level = True
        elif commands[0] == "Delete":
            self.records[int(commands[1])] = None
            self.indexes[int(commands[1])] = None
            is_row_level = True
        elif commands[0] == "Attributes":
             self.records[0] = commands[2].split(":")
             is_row_level = True
        elif commands[0] == "Update":
             self.records[int(commands[1])] = commands[2].split("->")[0].split(":")
             is_row_level = True
        elif commands[0] == "Add Column":
            self.records[0].append(commands[1])
            for idx in range(1,len(self.records)):
                self.records[idx].append(commands[2])
            is_row_level = False
        elif commands[0] == "Delete Column":
            cindex = self.records[0].indexOf(commands[1])
            for idx in range(len(self.records)):
                self.records[idx].remove(self.records[idx][cindex])
            is_row_level = False
        if is_row_level:
            if self.count<int(commands[1]):
                self.count = int(commands[1])

    def parse_data_as_restore(self):
        self.data = reversed(self.data)
        for line in self.data:
            try:
                self.parse_command(line)
            except Exception as e:
                self.report.error(e)
        self.dataframe = pd.DataFrame(self.records[1:self.count+1],index = self.indexes[1:self.count+1], columns = self.records[0])
        return pd.DataFrame(
            list(map(lambda y:list(y),list(filter(lambda x: None not in x,self.dataframe.values)))),
            columns = self.dataframe.columns
        )

    def dump(self, destination_url=None, destination_name=None):
        self.destination_url = destination_url
        self.destination_name = os.path.basename(self.destination_url) if destination_name is None else destination_name
        try:
            return self._dump_()
        except:
            raise DestinationEngineNotAvailableException(f"No Destination Found with url  :: {self.destination_url}")

    def _dump_(self):
        if self.destination_name[-3:] == "csv":
            try:
                self.dataframe.to_csv(self.destination_name, encoding='utf-8', index=False)
            except:
                self.manual_dump(destination_url=self.destination_url)
            finally:
                pass
            self.report.warning(f"Dumped to file successfully file url(csv): {self.destination_url}")
            return True
        elif self.destination_name[-4] == "xlsx":
            with pd.ExcelWriter(self.destination_name) as writer:
                self.dataframe.to_excel(writer)
            self.report.warning(f"Dumped to file successfully file url(excel): {self.destination_url}")
            return True
        else:
            raise DestinationEngineNotAvailableException(f"No Destination Found with url  :: {self.destination_url}")

    #@run_with_protected_environ
    def manual_dump(self, destination_url):
        with open(destination_url, "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(self.columns)
            writer.writerows(self.records)
        self.report.warning(f"Dumped to file successfully file url(csv): {destination_url}")


from typing import Deque
class Solution:
    def shortestSubarray(self, nums: List[int], k: int) -> int:
        count = -1
        sumn = [0]
        stack = [0]
        summa = 0
        for index in range(len(nums)):
            if nums[index] >= k:
                return 1
            summa += nums[index]
            sumn.append(summa)

        deq = Deque()
        temp_val = None
        for index in range(len(sumn)):

            if len(deq) == 0:
                deq.append(index)
            while len(deq) and sumn[index] - sumn[deq[0]] >= k:
                val = deq.popleft()
                if temp_val == None:
                    temp_val = index - val
                else:
                    temp_val = min(temp_val, index - val)
            while len(deq) and sumn[index] <= sumn[deq[-1]]:
                deq.pop()
            deq.append(index)
        return temp_val if temp_val != None else -1


# pylint: disable=unused-argument
"""
An abstract base class for MarketData instances.
A MarketData class is used for fetching live and historical market data.
"""
import enum
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional, Union

import pandas as pd

from aqua.security import Stock
from aqua.security.security import Security

class StreamType(enum.Enum):
    """The stream type for market data subscriptions"""

    QUOTES = "quotes"
    TRADES = "trades"

@dataclass
class Trade:
    """A single trade"""

    price: float = float("nan")
    size: int = float("nan")
    time: pd.Timestamp = pd.Timestamp("nat")

    def __post_init__(self):
        if self.size < 0:
            raise ValueError("Trade size can't be negative")

    def __repr__(self) -> str:
        return f"{self.price:.2f} x {self.size}"

@dataclass
class Quote:
    """A single quote"""

    bid: float = float("nan")
    bid_size: int = float("nan")
    ask: float = float("nan")
    ask_size: int = float("nan")
    time: pd.Timestamp = pd.Timestamp("nat")

    def __post_init__(self):
        if self.bid_size < 0 or self.ask_size < 0:
            raise ValueError(
                f"Bid and ask size can't be negative. Got {self.bid_size} x {self.ask_size}"
            )
        if self.bid_size == 0 and self.ask_size == 0:
            raise ValueError("Bid and ask sizes can't be both 0")

    def mid_price(self) -> float:
        """
        Calculates the size weighted mid price of a quote. Returns nan if quote isn't double sided
        """
        if self.bid_size == 0:
            return self.ask
        if self.ask_size == 0:
            return self.bid
        return (self.bid * self.ask_size + self.ask * self.bid_size) / (
            self.bid_size + self.ask_size
        )

    def __repr__(self) -> str:
        return f"{self.bid_size} x {self.bid:.2f} @ {self.ask:.2f} x {self.ask_size}"

class IMarketData(ABC):
    """
    Abstract base class for market data. Any functionality that's not supported will return
    `NotImplemented`.

    All Timestamps without a localized timezone will be treated as America/New_York time.
    """

    async def __aenter__(self) -> "IMarketData":
        """
        Sets up the market data connections
        """

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """
        Closes market data connections
        """

    # Metadata
    # --------

    @property
    @abstractmethod
    def name(self) -> str:
        """Returns a (unique) name for the market data"""

    # Price Data
    # ----------

    async def get_hist_bars(
        self,
        security: Security,
        bar_size: pd.Timedelta,
        start_date: pd.Timestamp,
        end_date: Optional[pd.Timestamp] = None,
    ) -> Union[pd.DataFrame, type(NotImplemented)]:
        """
        Gets historical bars for trades made on a given security over a time span. The trades
        are not adjusted for splits or dividends. Note that the start and end dates are inclusive.
        This means all trades made on `start_date` and `end_date` are considered as part of the
        aggregation.

        Returns pandas DataFrame with columns "Open", "High", "Low", "Close", "Volume",
        "NumTrades", and "VWAP". Some data sources may include more columns. If the parameters
        cannot be satisfied, `NotImplemented` is returned instead.

        The index of the DataFrame marks the start time of each bar.

        :param security: the security to get historical bars for
        :param start_date: the start date for trades. Note that the Timestamp is first converted
            to timezone America/New_York (or localized) and then rounded down to the latest day
            before the timestamp.
        :param end_date: the end date for trades (inclusive). The same timezone rounding rules for
            `start_date` applies to `end_date`. If not specified, then the end_date is assumed to
            be the same as the start date (so only 1 day of data will be fetched)
        :param bar_size: the duration of each bar
        :return: a pandas DataFrame with columns "Open", "High", "Low", "Close", "Volume",
            "NumTrades", and "VWAP" and index "Time". If the data source does not support the
            parameters specified, `NotImplemented` will be returned instead.
        :raise ValueError: if `end_date` is before `start_date` or if `bar_size` is not positive
        """
        return NotImplemented

    # Streaming data
    # --------------

    async def subscribe(
        self, stream_type: StreamType, security: Security
    ) -> Union[None, type(NotImplemented)]:
        """
        Subscribes to a stream of a given `stream_type` for a security. Calling subscribe on the
        same security multiple twice does nothing. Call `unsubscribe(...)` followed by
        `subscribe(...)` to refresh a subscription.

        :param stream_type: what to subscribe
        :param security: the security to subscribe to
        :return: None if subscription was successful. If the given stream type cannot be subscribed
            to for the given security, `NotImplemented` is returned.
        """
        return NotImplemented

    async def get(
        self, stream_type: StreamType, security: Security
    ) -> Union[Quote, Trade]:
        """
        Asynchronously gets the next value for a security subscription. Note that subscriptions
        can cause values to "pile up" if `get(...)` is not called frequent enough.

        :param stream_type: the stream type to get the next value for
        :param security: the security to get the next value for
        :return: depending on `stream_type`, a quote or trade object
        :raise ValueError: if the security was not subscribed to
        :raise NotImplementedError: if the stream type is not supported. (this doesn't return
            NotImplemented because the client should never call this before calling
            `subscribe(...)`)
        """
        raise NotImplementedError

    async def unsubscribe(self, stream_type: StreamType, security: Security) -> None:
        """
        Unsubscribes from a security. This will clear the internal buffer for incoming
        values.

        :param stream_type: the stream type to subscribe to
        :param security: the security to unsubscribe from
        :raise ValueError: if the security was not subscribed to
        """
        raise NotImplementedError

    # Fundamental Data
    # ----------------

    async def get_stock_dividends(
        self, stock: Stock
    ) -> Union[pd.DataFrame, type(NotImplemented)]:
        """
        Returns the dividend history of a stock as a pandas DataFrame

        The DataFrame has columns "Amount", "ExDate", "PaymentDate", and "RecordDate"

        :param stock: the stock to fetch dividends for
        :return: a pandas DataFrame
        """
        return NotImplemented

    async def get_stock_splits(
        self, stock: Stock
    ) -> Union[pd.DataFrame, type(NotImplemented)]:
        """
        Returns all stock splits as a pandas DataFrame.

        The DataFrame has columns "Ratio" and "ExDate"

        :param stock: the stock to fetch splits for
        :return: a pandas DataFrame
        """
        return NotImplemented

def _set_time(time: pd.Timestamp) -> pd.Timestamp:
    """Convert or localize timezone to New York"""
    if time.tz is None:
        return time.tz_localize("America/New_York")
    return time.tz_convert("America/New_York")


import cv2
from addPadding import *
from dividingImage import *
from pickleAndUnpickle import *
from testingAndTrainingPaths import *
from settings import *
from printDataBase import *
from skimage.io import sift
from matplotlib import pyplot as plt

def constructTrainingArray(fontsList, keyPointsFileName, partitioningType, constantWindowParameters, slidingWindowParameters):

    print("Constructing the database...")
    trainingDataPathsForAllFonts = []
    for font in fontsList:
        trainingDataPathsForOneFont = getTrainingImagesPaths(Font=font)
        trainingDataPathsForAllFonts.append(trainingDataPathsForOneFont)
    listForAllFonts=[]
    i = 0
    for eachFont in trainingDataPathsForAllFonts:
        listForFont = []
        for eachChar in eachFont:
            j = 0
            listForChar = []
            for eachShape in eachChar:
                listForShape = []
                img = cv2.imread(eachShape, 0)
                # cv2.imshow('img'+str(i),img)
                # cv2.waitKey(0)
                paddedImage = addPadding(img, horizontalPadding, verticalPadding)
                # cv2.imshow('img'+str(i+1),paddedImage)
                # cv2.waitKey(0)
                if partitioningType == "constant":
                    parts = divideImage(paddedImage, constantWindowParameters["n"], constantWindowParameters["m"])
                elif partitioningType == "sliding":
                    parts = slidingWindow(paddedImage, slidingWindowParameters["winWidth"]
                                          , slidingWindowParameters["winHeight"],
                                          slidingWindowParameters["shift"],
                                          slidingWindowParameters["shiftDirection"])

                ImagePartsKeyPoints_array = []
                for part in parts:
                    subImage = getSubImageData(paddedImage, part)
                    # Initiate SIFT detector
                    sift = cv2.xfeatures2d.SIFT_create()
                    kp1, des1 = sift.detectAndCompute(subImage, None)
                    temp =  pickle_keypoints(kp1, des1)
                    listForShape.append(temp)
                    # listForShape.append([kp1,des1])
                    # Initiate ORB detector
                    # orb = cv2.ORB_create()
                    # find the keypoints and descriptors with ORB
                    # kp1, des1 = orb.detectAndCompute(subImage, None)
                char, pos = getCharWithPositionFromPath(eachShape)
                listForChar.append((int(pos)-1,listForShape))
            listForFont.append(listForChar)
        listForAllFonts.append(listForFont)
    # print(listForAllFonts[0][1])
    storeToFile(databaseFolderName+keyPointsFileName, listForAllFonts)
    print("Database is successfully constructed.")


import pandas as pd
import os
import yaml
from util import Path, run_command

class Sanity_check(object):
    def __init__(self, config) -> None:
        self.samples = config['samples']
        self.path_to_dreem_output_files = config['path_to_dreem_output_files']
        self.sample_file = self.path_to_dreem_output_files+'samples.csv'
        self.library_file = self.path_to_dreem_output_files+'library.csv'
        self.path_to_rnastructure = config['rnastructure']['path']
        self.verbose = config['verbose']
        self.path = Path()
        self.config = config

    def files(self):
        # check that every file is there
        for s in self.samples:
            assert os.path.exists(self.path_to_dreem_output_files+s+'.csv'), f"{self.path_to_dreem_output_files+s+'.csv'} not found"
        if self.config['use_library']:
            assert os.path.exists(self.library_file), f"{self.library_file} not found"
        if self.config['use_samples']:
            assert os.path.exists(self.sample_file), f"{self.sample_file} not found"
        if self.config['use_rnastructure']:
            assert os.path.exists(self.path_to_rnastructure), f"{self.path_to_rnastructure} not found"

    def check_samples(self):
        if self.verbose: print(f"Checking {self.sample_file}")
        # check the sanity of samples.csv
        df = pd.read_csv(self.sample_file)
        assert len(df['sample']) == len(df['sample'].unique()), "Every line isn't unique in samples.csv"

        # check that every sample as a corresponding line of samples.csv
        df['sample'] = df['sample'].astype(str)

        for s in self.samples:
            assert s in list(df['sample']), f"{s, type(s)} doesn't have a corresponding line in samples.csv"

        with open(self.path.sample_attribute_path, 'r') as f:
            sample_attributes = yaml.safe_load(f)

        assert len(df['exp_env'].unique()) == 1, "exp_env is not unique in samples.csv"
        exp_env = df['exp_env'].unique()[0]

        # check that every column of samples.csv is in sample_attributes.yml
        assert exp_env in ['in_vivo','in_vitro'], f"{exp_env} is not a valid value for exp_env. Should be in_vivo or in_vitro"
        # Check that you have all mandatory columns

        for mand in sample_attributes['mandatory']['all'] + sample_attributes['mandatory'][exp_env]:
            assert mand in list(df.columns), f"{mand} is not in samples.csv"

        # check that every mandatory column of samples.csv is not empty for every sample
        for mand in sample_attributes['mandatory']['all'] + sample_attributes['mandatory'][exp_env]:
            for s in self.samples:
                assert df[df['sample']==s][mand].isnull().sum() == 0, f"{mand} is empty in samples.csv for sample {s}"

        df.to_csv(self.config['temp_folder']+'/samples.csv', index=False)
        if self.verbose: print('Checking samples.csv done\n')
        return 1

    def check_library(self):
        # check the sanity of libraries.csv
        if self.verbose: print(f"Checking library.csv")
        assert os.path.exists(self.library_file), f"{self.library_file} not found"
        df = pd.read_csv(self.library_file)
        assert 'construct' in list(df.columns), "construct is not in library.csv"
        df.to_csv(f"{self.config['temp_folder']}/library.csv", index=False)
        if self.verbose: print(f"Checking library.csv done\n")

    def make_folder(self):
       if not os.path.exists( self.config['temp_folder']):
            os.makedirs( self.config['temp_folder'])

    def run(self):
        self.make_folder()
        if self.verbose: print("Checking files")
        self.files()
        if self.config['use_library']:
            self.check_library()
        if self.config['use_samples']:
            self.check_samples()
        if self.verbose: print("Checking files done\n")
        return 1


import argparse
from pathlib import Path

from PIL import Image

print('Calculate comic book dataset stats\n')

def pages_glob(dataset_path: Path):
    return dataset_path.glob('*.jpg')

def pages_average_size(dataset_path: Path):
    count = 0

    w_sum = 0
    h_sum = 0

    for img_path in pages_glob(dataset_path):
        count += 1

        with Image.open(img_path) as img:
            w_sum += img.width
            h_sum += img.height

    return (round(w_sum / count), round(h_sum / count))

def run(dataset_path: Path):
    dataset_path = dataset_path.absolute()

    classes = {}

    with open(dataset_path.joinpath('classes.txt'), 'r') as txt:
        for i, line in enumerate(txt.readlines()):
            classes[i] = line.replace('\n', '')

    if len(classes) == 0:
        raise RuntimeError('Provide YOLO classes.txt')

    pages_count = len(list(pages_glob(dataset_path)))

    print(f"Total pages count: {pages_count}")

    # Paths to YOLO data .txt files
    pages_yolo_data_paths = list(
        filter(lambda file: file.name != 'classes.txt' and file.stat().st_size != 0, dataset_path.glob('*.txt')))

    print(f"Pages with YOLO data count: {len(pages_yolo_data_paths)}")

    avr_w, avr_h = pages_average_size(dataset_path)

    print(f"Pages average size: ({avr_w}, {avr_h})")
    print(f"Pages average aspect ratio: {avr_w/avr_h}")

    obj_count = {}

    for page_yolo_path in pages_yolo_data_paths:
        with open(page_yolo_path, 'r') as page_yolo_data:
            for obj_line in page_yolo_data.readlines():
                yolo_obj = int(obj_line.split(' ', 1)[0])

                obj_count[classes[yolo_obj]] = obj_count.get(
                    classes[yolo_obj], 0) + 1

    print('\nObjects:')

    for obj, count in obj_count.items():
        print(f"'{obj}' count: {count}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Calculate YOLO dataset stats (like pages count, size, etc.)')

    parser.add_argument('--dataset',
                        default='./yolo/dataset/',
                        help='Path to the YOLO dataset')

    args = parser.parse_args()

    run(Path(args.dataset))


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('core', '0020_schedule_scheduletime'),
    ]

    operations = [
        migrations.AlterField(
            model_name='schedule',
            name='day',
            field=models.CharField(max_length=3, choices=[('MON', 'Monday'), ('TUE', 'Tuesday'), ('WED', 'Wednesday'), ('THU', 'Thursday'), ('FRI', 'Friday'), ('SAT', 'Saturday'), ('SUN', 'Sunday')]),
        ),
    ]


# Standard Packages
import json
import os

# Custom Packages
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import streamlit as st

# Modules
import dataset

st.title("NLP Dataset Streamlit")

# Select an existing/supported datasets
if dataset.supported[0]["id"] is not None:
    dataset.supported.insert(0, {"id": None, "name": ""})  # add a blank entry

selected_dataset = st.sidebar.selectbox(
    "Choose a file", dataset.supported, format_func=lambda x: x["name"]
)

# Read the dataset as a DataFrame
# Or upload a file
st.sidebar.write("or")

uploaded_file = st.sidebar.file_uploader("Upload a file")

if uploaded_file is not None:
    print(upload_file)

# Load dataset
data = []

if selected_dataset["id"] is not None:
    # List available files
    files = dataset.list_files(selected_dataset["directory"])
    selected_files = st.sidebar.multiselect(
        "Select set",
        files,
        default=None if len(files) > 1 else files[0],
        format_func=os.path.basename,
    )

    data_load_state = st.text("Loading data...")
    data = dataset.load_files(selected_files)
    data_load_state.text("Loading data... done!")

# Options
if data:
    # Raw data
    raw_data = st.sidebar.checkbox("Raw data", value=True)
    if raw_data:
        n_rows = st.sidebar.number_input("# of rows", min_value=1, value=10)

    # Sequence length
    sequence_length = st.sidebar.checkbox("Sequence length", value=True)
    if sequence_length:
        cols = zip(data[0]["df"].columns, data[0]["df"].dtypes)
        cols = [col[0] for col in cols if col[1] is np.dtype("O")]
        sequence_length_columns = st.sidebar.multiselect("Select columns", cols)
        n_bins = st.sidebar.number_input("# bins", min_value=1, value=20)

for entry in data:
    st.header(entry["name"])
    df = entry["df"]

    # Simple stats
    st.write(f"Number of elements: {len(df)}")

    # Display raw data
    if raw_data:
        st.subheader("Raw data")
        st.write(df.sample(min(n_rows, len(df))))

    # Histogram of sequence length
    if sequence_length:
        fig, ax = plt.subplots(1, 1)

        for col in sequence_length_columns:
            ax.hist(
                [
                    len(elem.split() if hasattr(elem, "split") else elem)
                    for elem in df[col]
                ],
                bins=n_bins,
                histtype="bar",
                alpha=0.5,
            )

        ax.set_xlabel("Length")
        ax.set_ylabel("Frequency")
        ax.grid()
        ax.legend(sequence_length_columns)

        st.pyplot(fig)


import asyncio
from asyncio.events import AbstractEventLoop
from asyncio.futures import Future
from types import FunctionType
from typing import Union
from wmp.handler import parse

class Transport(asyncio.Protocol):
    def __init__(self, loop: AbstractEventLoop, callback: FunctionType = None):
        self.transport = None
        self.loop = loop
        self.callback: Union[None, FunctionType] = callback
        self.next = None
        self.disconnection_callback = None

    def connection_made(self, transport):
        self.transport = transport

    def connection_lost(self, exception):
        self.transport = None
        if self.disconnection_callback is not None:
            self.disconnection_callback()

    def data_received(self, data):
        lines = data.decode("utf-8").strip().split("\r\n")

        # If the command looks like a configuration change, let the callback handler
        # deal with that, other wise ship it off to the
        results = list(map(lambda line: parse(line), lines))

        if self.next is not None:
            if len(results) == 1:
                self.next.set_result(results[0])
            else:
                self.next.set_result(results)

            self.next = None
        else:
            if self.callback:
                for result in results:
                    self.callback(result)

    def send(self, message: str) -> Future:
        if self.transport is not None:
            self.next = self.loop.create_future()
            self.transport.write(bytes(message + "\r\n", "UTF-8"))
            return self.next
        else:
            future = self.loop.create_future()
            future.set_result(None)
            return future