# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import sys

from ipdl.cgen import CodePrinter
from ipdl.cxx.ast import MethodSpec, TypeArray, Visitor, DestructorDecl


class CxxCodeGen(CodePrinter, Visitor):
    def __init__(self, outf=sys.stdout, indentCols=4):
        CodePrinter.__init__(self, outf, indentCols)

    def cgen(self, cxxfile):
        cxxfile.accept(self)

    def visitWhitespace(self, ws):
        if ws.indent:
            self.printdent("")
        self.write(ws.ws)

    def visitVerbatimNode(self, verb):
        if verb.indent:
            self.printdent("")
        self.write(verb.text)

    def visitGroupNode(self, group):
        offsetCols = self.indentCols * group.offset
        self.col += offsetCols
        for node in group.nodes:
            node.accept(self)
        self.col -= offsetCols

    def visitCppDirective(self, cd):
        if cd.rest:
            self.println("#%s %s" % (cd.directive, cd.rest))
        else:
            self.println("#%s" % (cd.directive))

    def visitNamespace(self, ns):
        self.println("namespace " + ns.name + " {")
        self.visitBlock(ns)
        self.println("} // namespace " + ns.name)

    def visitType(self, t):
        if t.const:
            self.write("const ")

        self.write(t.name)

        if t.T is not None:
            self.write("<")
            if type(t.T) is list:
                t.T[0].accept(self)
                for tt in t.T[1:]:
                    self.write(", ")
                    tt.accept(self)
            else:
                t.T.accept(self)
            self.write(">")

        if t.inner is not None:
            self.write("::")
            t.inner.accept(self)

        ts = ""
        if t.ptr:
            ts += "*"
        elif t.ptrptr:
            ts += "**"
        elif t.ptrconstptr:
            ts += "* const*"

        if t.ref:
            ts += "&"
        elif t.rvalref:
            ts += "&&"
        elif t.rightconst:
            ts += " const"

        self.write(ts)

    def visitTypeEnum(self, te):
        self.write("enum")
        if te.name:
            self.write(" " + te.name)
        self.println(" {")

        self.indent()
        nids = len(te.idnums)
        for i, (id, num) in enumerate(te.idnums):
            self.printdent(id)
            if num:
                self.write(" = " + str(num))
            if i != (nids - 1):
                self.write(",")
            self.println()
        self.dedent()
        self.printdent("}")

    def visitTypeUnion(self, u):
        self.write("union")
        if u.name:
            self.write(" " + u.name)
        self.println(" {")

        self.indent()
        for decl in u.components:
            self.printdent()
            decl.accept(self)
            self.println(";")
        self.dedent()

        self.printdent("}")

    def visitTypeFunction(self, fn):
        self.write("std::function<")
        fn.ret.accept(self)
        self.write("(")
        self.writeDeclList(fn.params)
        self.write(")>")

    def visitTypedef(self, td):
        if td.templateargs:
            formals = ", ".join(["class " + T for T in td.templateargs])
            args = ", ".join(td.templateargs)
            self.printdent("template<" + formals + "> using " + td.totypename + " = ")
            td.fromtype.accept(self)
            self.println("<" + args + ">;")
        else:
            self.printdent("typedef ")
            td.fromtype.accept(self)
            self.println(" " + td.totypename + ";")

    def visitUsing(self, us):
        self.printdent("using ")
        us.type.accept(self)
        self.println(";")

    def visitForwardDecl(self, fd):
        if fd.cls:
            self.printdent("class ")
        elif fd.struct:
            self.printdent("struct ")
        self.write(str(fd.pqname))
        self.println(";")

    def visitDecl(self, d):
        # C-syntax arrays make code generation much more annoying
        if isinstance(d.type, TypeArray):
            d.type.basetype.accept(self)
        else:
            d.type.accept(self)

        if d.name:
            self.write(" " + d.name)

        if isinstance(d.type, TypeArray):
            self.write("[")
            d.type.nmemb.accept(self)
            self.write("]")

    def visitParam(self, p):
        self.visitDecl(p)
        if p.default is not None:
            self.write(" = ")
            p.default.accept(self)

    def visitClass(self, c):
        if c.specializes is not None:
            self.printdentln("template<>")

        if c.struct:
            self.printdent("struct")
        else:
            self.printdent("class")
        self.write(" " + c.name)
        if c.final:
            self.write(" final")

        if c.specializes is not None:
            self.write(" <")
            c.specializes.accept(self)
            self.write(">")

        ninh = len(c.inherits)
        if 0 < ninh:
            self.println(" :")
            self.indent()
            for i, inherit in enumerate(c.inherits):
                self.printdent()
                inherit.accept(self)
                if i != (ninh - 1):
                    self.println(",")
            self.dedent()
        self.println()

        self.printdentln("{")
        self.indent()

        self.visitBlock(c)

        self.dedent()
        self.printdentln("};")

    def visitInherit(self, inh):
        self.write(inh.viz + " ")
        inh.type.accept(self)

    def visitFriendClassDecl(self, fcd):
        self.printdentln("friend class " + fcd.friend + ";")

    def visitMethodDecl(self, md):
        if md.T:
            self.write("template<")
            self.write("typename ")
            md.T.accept(self)
            self.println(">")
            self.printdent()

        if md.warn_unused:
            self.write("[[nodiscard]] ")

        if md.methodspec == MethodSpec.STATIC:
            self.write("static ")
        elif md.methodspec == MethodSpec.VIRTUAL or md.methodspec == MethodSpec.PURE:
            self.write("virtual ")

        if md.ret:
            if md.only_for_definition:
                self.write("auto ")
            else:
                md.ret.accept(self)
                self.println()
                self.printdent()

        if md.cls is not None:
            assert md.only_for_definition

            self.write(md.cls.name)
            if md.cls.specializes is not None:
                self.write("<")
                md.cls.specializes.accept(self)
                self.write(">")
            self.write("::")

        if md.typeop is not None:
            self.write("operator ")
            md.typeop.accept(self)
        else:
            if isinstance(md, DestructorDecl):
                self.write("~")
            self.write(md.name)

        self.write("(")
        self.writeDeclList(md.params)
        self.write(")")

        if md.const:
            self.write(" const")
        if md.ret and md.only_for_definition:
            self.write(" -> ")
            md.ret.accept(self)

        if md.methodspec == MethodSpec.OVERRIDE:
            self.write(" override")
        elif md.methodspec == MethodSpec.PURE:
            self.write(" = 0")

    def visitMethodDefn(self, md):
        # Method specifiers are for decls, not defns.
        assert md.decl.methodspec == MethodSpec.NONE

        self.printdent()
        md.decl.accept(self)
        self.println()

        self.printdentln("{")
        self.indent()
        self.visitBlock(md)
        self.dedent()
        self.printdentln("}")

    def visitConstructorDecl(self, cd):
        if cd.explicit:
            self.write("explicit ")
        else:
            self.write("MOZ_IMPLICIT ")
        self.visitMethodDecl(cd)

    def visitConstructorDefn(self, cd):
        self.printdent()
        cd.decl.accept(self)
        if len(cd.memberinits):
            self.println(" :")
            self.indent()
            ninits = len(cd.memberinits)
            for i, init in enumerate(cd.memberinits):
                self.printdent()
                init.accept(self)
                if i != (ninits - 1):
                    self.println(",")
            self.dedent()
        self.println()

        self.printdentln("{")
        self.indent()

        self.visitBlock(cd)

        self.dedent()
        self.printdentln("}")

    def visitDestructorDecl(self, dd):
        self.visitMethodDecl(dd)

    def visitDestructorDefn(self, dd):
        self.printdent()
        dd.decl.accept(self)
        self.println()

        self.printdentln("{")
        self.indent()

        self.visitBlock(dd)

        self.dedent()
        self.printdentln("}")

    def visitExprLiteral(self, el):
        self.write(str(el))

    def visitExprVar(self, ev):
        self.write(ev.name)

    def visitExprPrefixUnop(self, e):
        self.write("(")
        self.write(e.op)
        self.write("(")
        e.expr.accept(self)
        self.write(")")
        self.write(")")

    def visitExprCast(self, c):
        if c.static:
            pfx, sfx = "static_cast<", ">"
        else:
            assert c.const
            pfx, sfx = "const_cast<", ">"
        self.write(pfx)
        c.type.accept(self)
        self.write(sfx + "(")
        c.expr.accept(self)
        self.write(")")

    def visitExprBinary(self, e):
        self.write("(")
        e.left.accept(self)
        self.write(") " + e.op + " (")
        e.right.accept(self)
        self.write(")")

    def visitExprConditional(self, c):
        self.write("(")
        c.cond.accept(self)
        self.write(" ? ")
        c.ife.accept(self)
        self.write(" : ")
        c.elsee.accept(self)
        self.write(")")

    def visitExprSelect(self, es):
        self.write("(")
        es.obj.accept(self)
        self.write(")")
        self.write(es.op)
        es.field.accept(self)

    def visitExprAssn(self, ea):
        ea.lhs.accept(self)
        self.write(" " + ea.op + " ")
        ea.rhs.accept(self)

    def visitExprCall(self, ec):
        ec.func.accept(self)
        self.write("(")
        self.writeExprList(ec.args)
        self.write(")")

    def visitExprNew(self, en):
        self.write("new ")
        if en.newargs is not None:
            self.write("(")
            self.writeExprList(en.newargs)
            self.write(") ")
        en.ctype.accept(self)
        if en.args is not None:
            self.write("(")
            self.writeExprList(en.args)
            self.write(")")

    def visitExprDelete(self, ed):
        self.write("delete ")
        ed.obj.accept(self)

    def visitExprLambda(self, l):
        self.write("[")
        ncaptures = len(l.captures)
        for i, c in enumerate(l.captures):
            c.accept(self)
            if i != (ncaptures - 1):
                self.write(", ")
        self.write("](")
        self.writeDeclList(l.params)
        self.write(")")
        if l.ret:
            self.write(" -> ")
            l.ret.accept(self)
        self.println(" {")
        self.indent()
        self.visitBlock(l)
        self.dedent()
        self.printdent("}")

    def visitStmtBlock(self, b):
        self.printdentln("{")
        self.indent()
        self.visitBlock(b)
        self.dedent()
        self.printdentln("}")

    def visitLabel(self, label):
        self.dedent()  # better not be at global scope ...
        self.printdentln(label.name + ":")
        self.indent()

    def visitCaseLabel(self, cl):
        self.dedent()
        self.printdentln("case " + cl.name + ":")
        self.indent()

    def visitDefaultLabel(self, dl):
        self.dedent()
        self.printdentln("default:")
        self.indent()

    def visitStmtIf(self, si):
        self.printdent("if (")
        si.cond.accept(self)
        self.println(") {")
        self.indent()
        si.ifb.accept(self)
        self.dedent()
        self.printdentln("}")

        if si.elseb is not None:
            self.printdentln("else {")
            self.indent()
            si.elseb.accept(self)
            self.dedent()
            self.printdentln("}")

    def visitStmtFor(self, sf):
        self.printdent("for (")
        if sf.init is not None:
            sf.init.accept(self)
        self.write("; ")
        if sf.cond is not None:
            sf.cond.accept(self)
        self.write("; ")
        if sf.update is not None:
            sf.update.accept(self)
        self.println(") {")

        self.indent()
        self.visitBlock(sf)
        self.dedent()
        self.printdentln("}")

    def visitStmtRangedFor(self, rf):
        self.printdent("for (auto& ")
        rf.var.accept(self)
        self.write(" : ")
        rf.iteree.accept(self)
        self.println(") {")

        self.indent()
        self.visitBlock(rf)
        self.dedent()
        self.printdentln("}")

    def visitStmtSwitch(self, sw):
        self.printdent("switch (")
        sw.expr.accept(self)
        self.println(") {")
        self.indent()
        self.visitBlock(sw)
        self.dedent()
        self.printdentln("}")

    def visitStmtBreak(self, sb):
        self.printdentln("break;")

    def visitStmtDecl(self, sd):
        self.printdent()
        sd.decl.accept(self)
        if sd.initargs is not None:
            self.write("{")
            self.writeDeclList(sd.initargs)
            self.write("}")
        if sd.init is not None:
            self.write(" = ")
            sd.init.accept(self)
        self.println(";")

    def visitStmtExpr(self, se):
        self.printdent()
        se.expr.accept(self)
        self.println(";")

    def visitStmtReturn(self, sr):
        self.printdent("return")
        if sr.expr:
            self.write(" ")
            sr.expr.accept(self)
        self.println(";")

    def writeDeclList(self, decls):
        # FIXME/cjones: try to do nice formatting of these guys

        ndecls = len(decls)
        if 0 == ndecls:
            return
        elif 1 == ndecls:
            decls[0].accept(self)
            return

        self.indent()
        self.indent()
        for i, decl in enumerate(decls):
            self.println()
            self.printdent()
            decl.accept(self)
            if i != (ndecls - 1):
                self.write(",")
        self.dedent()
        self.dedent()

    def writeExprList(self, exprs):
        # FIXME/cjones: try to do nice formatting and share code with
        # writeDeclList()
        nexprs = len(exprs)
        for i, expr in enumerate(exprs):
            expr.accept(self)
            if i != (nexprs - 1):
                self.write(", ")
