# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
from . import gen
from .gen.AgtypeLexer import AgtypeLexer
from .gen.AgtypeParser import AgtypeParser
from .gen.AgtypeVisitor import AgtypeVisitor
from .models import *
from .exceptions import *
from antlr4 import *
from antlr4.tree.Tree import *
from decimal import Decimal

resultHandler = None

class ResultHandler:
    def parse(ageData):
        pass

def newResultHandler(query=""):
    resultHandler = Antlr4ResultHandler(None, query)
    return resultHandler

def parseAgeValue(value, cursor=None):
    if value is None:
        return None

    global resultHandler
    if (resultHandler == None):
        resultHandler = Antlr4ResultHandler(None)
    try:
        return resultHandler.parse(value)
    except Exception as ex:
        raise AGTypeError(value)


class Antlr4ResultHandler(ResultHandler):
    def __init__(self, vertexCache, query=None):
        self.lexer = AgtypeLexer()
        self.parser = AgtypeParser(None)
        self.visitor = ResultVisitor(vertexCache)

    def parse(self, ageData):
        if not ageData:
            return None
        # print("Parse::", ageData)

        self.lexer.inputStream = InputStream(ageData)
        self.parser.setTokenStream(CommonTokenStream(self.lexer))
        self.parser.reset()
        tree = self.parser.agType()
        parsed = tree.accept(self.visitor)
        return parsed


# print raw result String
class DummyResultHandler(ResultHandler):
    def parse(self, ageData):
        print(ageData)

# default agType visitor
class ResultVisitor(AgtypeVisitor):
    vertexCache = None

    def __init__(self, cache) -> None:
        super().__init__()
        self.vertexCache = cache

    
    def visitAgType(self, ctx:AgtypeParser.AgTypeContext):
        agVal = ctx.agValue()
        if agVal != None:
            obj = ctx.agValue().accept(self)
            return obj

        return None

    def visitAgValue(self, ctx:AgtypeParser.AgValueContext):
        annoCtx = ctx.typeAnnotation()
        valueCtx = ctx.value()

        if annoCtx is not None:
            annoCtx.accept(self)
            anno = annoCtx.IDENT().getText()
            return self.handleAnnotatedValue(anno, valueCtx)
        else:
            return valueCtx.accept(self)


    # Visit a parse tree produced by AgtypeParser#StringValue.
    def visitStringValue(self, ctx:AgtypeParser.StringValueContext):
        return ctx.STRING().getText().strip('"')


    # Visit a parse tree produced by AgtypeParser#IntegerValue.
    def visitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext):
        return int(ctx.INTEGER().getText())

    # Visit a parse tree produced by AgtypeParser#floatLiteral.
    def visitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):
        c = ctx.getChild(0)
        tp = c.symbol.type
        text = ctx.getText()
        if tp == AgtypeParser.RegularFloat:
            return float(text)
        elif tp == AgtypeParser.ExponentFloat:
            return float(text)
        else:
            if text == 'NaN':
                return float('nan')
            elif text == '-Infinity':
                return float('-inf')
            elif text == 'Infinity':
                return float('inf')
            else:
                return Exception("Unknown float expression:"+text)
        

    # Visit a parse tree produced by AgtypeParser#TrueBoolean.
    def visitTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext):
        return True


    # Visit a parse tree produced by AgtypeParser#FalseBoolean.
    def visitFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext):
        return False


    # Visit a parse tree produced by AgtypeParser#NullValue.
    def visitNullValue(self, ctx:AgtypeParser.NullValueContext):
        return None


    # Visit a parse tree produced by AgtypeParser#obj.
    def visitObj(self, ctx:AgtypeParser.ObjContext):
        obj = dict()
        for c in ctx.getChildren():
            if isinstance(c, AgtypeParser.PairContext):
                namVal = self.visitPair(c)
                name = namVal[0]
                valCtx = namVal[1]
                val = valCtx.accept(self) 
                obj[name] = val
        return obj


    # Visit a parse tree produced by AgtypeParser#pair.
    def visitPair(self, ctx:AgtypeParser.PairContext):
        self.visitChildren(ctx)
        return (ctx.STRING().getText().strip('"') , ctx.agValue())


    # Visit a parse tree produced by AgtypeParser#array.
    def visitArray(self, ctx:AgtypeParser.ArrayContext):
        li = list()
        for c in ctx.getChildren():
            if not isinstance(c, TerminalNode):
                val = c.accept(self)
                li.append(val)
        return li

    def handleAnnotatedValue(self, anno:str, ctx:ParserRuleContext):
        if anno == "numeric":
            return Decimal(ctx.getText())
        elif anno == "vertex":
            dict = ctx.accept(self)
            vid = dict["id"]
            vertex = None
            if self.vertexCache != None and vid in self.vertexCache :
                vertex = self.vertexCache[vid]
            else:
                vertex = Vertex()
                vertex.id = dict["id"]
                vertex.label = dict["label"]
                vertex.properties = dict["properties"]
            
            if self.vertexCache != None:
                self.vertexCache[vid] = vertex

            return vertex
        
        elif anno == "edge":
            edge = Edge()
            dict = ctx.accept(self)
            edge.id = dict["id"]
            edge.label = dict["label"]
            edge.end_id = dict["end_id"]
            edge.start_id = dict["start_id"]
            edge.properties = dict["properties"]
            
            return edge

        elif anno == "path":
            arr = ctx.accept(self)
            path = Path(arr)
            
            return path

        return ctx.accept(self)
