#!/usr/bin/env python

import sys
import json
import argparse
import re
import logging
from graphql import language
from graphql.language import parse
from graphql.language.printer import print_ast


TYPE_PREFIX = "GQLT"
FUNC_PREFIX = "GQLF"


class GQLObjField:
    types_map = {
                  "String": "string",
                  "Boolean": "boolean",
                  "Int": "integer",
                  "Any": "any",
                  "Float": "number"
                }

    def __init__(self, field):
        self.name = field.name.value if field.kind in ("field_definition", "input_value_definition") else None
        self.raml_type = None
        self.gql_type = None
        self.mandatory = False
        self.list = None

        logging.debug("field: %s - %s" % (str(field.to_dict()), field.kind))

        t = field

        while t:
            if t.kind in ("field_definition", "input_value_definition"):
                t = t.type
                continue
            elif t.kind == "non_null_type":
                self.mandatory = True
                t = t.type
                continue
            elif t.kind == "list_type":
                self.list = GQLObjField(t.type)
                break
            elif t.kind == "named_type":
                self.gql_type = t.name.value
                self.raml_type = self.types_map.get(self.gql_type, TYPE_PREFIX + self.gql_type)
                break
            logging.error("Panic: unsupported kind: %s" % t.kind)
            sys.exit(-1)

    def render_field_type_raml(self, pfx=""):
        ret = ""
        if self.list:
            ret += "\n"
            ret += "%s      type: array\n" % pfx
            ret += "%s      items:" % pfx
            if self.list.list:
                ret += "%s" % self.list.render_field_type_raml(pfx * 2)
            else:
                ret += "\n%s        type:%s" % (pfx, self.list.render_field_type_raml(pfx * 2))
        else:
            ret += " %s" % self.raml_type
        return ret

    def render_field_raml(self, pfx=""):
        ret = "%s    %s%s:" % (pfx, self.name, "" if self.mandatory else "?")
        ret += self.render_field_type_raml(pfx)
        return ret

    def render_field_type_gql(self):
        ret = ""
        if self.list:
            ret += "[%s]" % (self.list.render_field_type_gql())
        else:
            ret += self.gql_type
        ret += "%s" % ("!" if self.mandatory else "")
        return ret

    def render_field_gql(self, pfx=""):
        return "%s%s: %s" % (pfx, self.name, self.render_field_type_gql())


class GQLObjType:
    def __init__(self, name, kind):
        self.kind = kind
        self.gql_name = name
        self.raml_name = TYPE_PREFIX + name
        self.fields = []

    def add_field(self, field):
        self.fields.append(field)

    def render_type_raml(self, pfx="  "):
        if not len(self.fields):
            return ""

        ret = ["%s%s:" % (pfx, self.raml_name)]
        ret.append("%s  properties:" % pfx)
        for f in self.fields:
            ret.append(f.render_field_raml(pfx=pfx))
        return "\n".join(ret) + "\n"

    def render_type_gql(self, pfx=""):
        if not len(self.fields):
            return ""

        ret = ["%s%s %s {" % (pfx, "input" if self.kind == "input_object_type_definition" else "type", self.gql_name)]
        for f in self.fields:
            ret.append("	%s" % f.render_field_gql())
        ret.append("}\n")
        return "\n".join(ret)


class GQLFuncType:
    def __init__(self, field):
        self.gql_name = field.name.value
        self.raml_name = FUNC_PREFIX + self.gql_name

        self.params = GQLObjField(field.arguments[0])
        self.returns = GQLObjField(field.type)

    def camel_to_snake(self, name):
        return re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()

    def render_func_raml(self):
        ret = "  %s:\n" % self.raml_name
        ret += "    type: dts.DTSFunction\n"
        ret += "    (cti.cti): cti.a.p.dts.func.v1.0~a.p.gql.%s.v0.1\n" % self.camel_to_snake(self.gql_name)
        ret += "    description: DTS function wrapper for GraphQL v1 method %s\n" % (self.gql_name)
        ret += "\n"
        ret += "    properties:\n"
        ret += "      params%s:%s\n" % ("" if self.params.mandatory else "?", self.params.render_field_type_raml("  "))
        ret += "      returns%s:%s\n" % ("" if self.returns.mandatory else "?", self.returns.render_field_type_raml("  "))
        ret += "\n"
        ret += "    cti-traits:\n"
        ret += "      deterministic_behavior: QUERY\n"
        ret += "      return: $graphql_v1[%s](data=$.params)\n" % self.gql_name
        ret += "\n"
        return ret

    def render_func_gql(self):
        ret = "	%s(data: %s): %s\n" % (self.gql_name, self.params.render_field_type_gql(), self.returns.render_field_type_gql())
        return ret


class GQLFile:
    def __init__(self):
        self.types = []
        self.functions = []

    def parse_definition(self, definition):

        t = GQLObjType(definition.name.value, definition.kind)

        for f in definition.fields:
            if definition.kind == "object_type_definition" and len(f.arguments):
                if t.gql_name == "Query":
                    self.functions.append(GQLFuncType(f))
            elif f.kind in ("input_value_definition", "field_definition"):
                t.add_field(GQLObjField(f))

        self.types.append(t)
        logging.debug(t.render_type_raml())

    def parse(self, gql_file_name):
        file = open(gql_file_name, "r")
        schema = file.read()
        file.close()

        ast = parse(schema)

        for definition in ast.definitions:
            logging.debug("%s/%s/%s" % (definition.name.value, print_ast(definition), json.dumps(definition.to_dict(), indent=4)))

            if definition.kind in ("object_type_definition", "input_object_type_definition"):
                self.parse_definition(definition)

    def render_raml(self, raml_file_name):
        print("Dumping output to: %s" % raml_file_name)

        file = open(raml_file_name, "w+")

        header = "#%RAML 1.0 Library\n" + \
                 "\n" + \
                 "uses:\n" + \
                 "  cti: ../../../.ramlx/cti.raml\n" + \
                 "  dts: ../../../dts/types.raml\n" + \
                 "\n" + \
                 "types:"

        file.write(header)
        for t in self.types:
            file.write(t.render_type_raml() + "\n")
        for f in self.functions:
            file.write(f.render_func_raml())
        file.close()

    def render_gql(self, gql_file_name):
        print("Dumping output to: %s" % gql_file_name)

        file = open(gql_file_name, "w+")

        file.write("type Query {\n")
        for f in self.functions:
            file.write(f.render_func_gql())
        file.write("}\n")
        for t in self.types:
            file.write(t.render_type_gql())
        file.close()


def main():
    parser = argparse.ArgumentParser(description='Acronis Healthcheck Polling Tool')

    parser.add_argument('-v', '--verbose', action='count', help='enable verbose mode (use -vv for max verbosity)')
    parser.add_argument('-i', '--input-gql-file', help="input: GraphQL definitions file")
    parser.add_argument('-O', '--output-gql-file', help="output: GraphQL definitions file (can be used for validation)")
    parser.add_argument('-o', '--output-raml-file', help="output: RAML definitions file")

    opts = parser.parse_args()

    if opts.verbose is None:
        level = logging.WARNING
    else:
        level = logging.DEBUG if opts.verbose > 1 else logging.INFO
    logging.basicConfig(level=level, filename=None)

    if not opts.input_gql_file:
        logging.error("the -f | --input-gql-file option is mandatory")
        sys.exit(-1)

    f = GQLFile()
    f.parse(opts.input_gql_file)
    if opts.output_raml_file:
        f.render_raml(opts.output_raml_file)
    if opts.output_gql_file:
        f.render_gql(opts.output_gql_file)
    if not opts.output_raml_file and not opts.output_gql_file:
        f.render_raml(sys.stdout)


if __name__ == '__main__':
    main()
