import os
import re

from clang_index import Clang_Index


class cgo(object):
    def get_inline_source(buffer):
        # TODO(zchee): very slow. about 100ms

        if 'import "C"' not in buffer:
            return (0, "")

        pos_import_c = list(buffer).index('import "C"')
        c_inline = buffer[:pos_import_c]

        if c_inline[len(c_inline) - 1] == "*/":
            comment_start = next(
                i
                for i, v in zip(range(len(c_inline) - 1, 0, -1), reversed(c_inline))
                if v == "/*"
            )
            c_inline = c_inline[comment_start + 1 : len(c_inline) - 1]

        return (len(c_inline), "\n".join(c_inline))

    def get_pkgconfig(packages):
        out = []
        pkgconfig = cgo.find_binary_path("pkg-config")
        if pkgconfig != "":
            for pkg in packages:
                flag = os.popen(pkgconfig + " " + pkg + " --cflags --libs").read()
                out += flag.rstrip().split(" ")
        return out

    def parse_candidates(result):
        completion = {"dup": 1, "word": ""}
        _type = ""
        word = ""
        placeholder = ""
        sep = " "

        for chunk in [x for x in result.string if x.spelling]:
            chunk_spelling = chunk.spelling

            # ignore inline fake main(void), and '_' prefix function
            if chunk_spelling == "main" or chunk_spelling.find("_") == 0:
                return completion

            if chunk.isKindTypedText():
                word += chunk_spelling
                placeholder += chunk_spelling
            elif chunk.isKindResultType():
                _type += chunk_spelling
            else:
                placeholder += chunk_spelling

        completion["word"] = word
        completion["abbr"] = completion["info"] = placeholder + sep + _type

        completion["kind"] = " ".join(
            [
                (
                    Clang_Index.kinds[result.cursorKind]
                    if (result.cursorKind in Clang_Index.kinds)
                    else str(result.cursorKind)
                )
            ]
        )

        return completion

    def complete(index, cache, cgo_options, line_count, source):
        cgo_pattern = r"#cgo (\S+): (.+)"
        flags = set()
        for key, value in re.findall(cgo_pattern, source):
            if key == "pkg-config":
                for flag in cgo.get_pkgconfig(value.split()):
                    flags.add(flag)
            else:
                if "${SRCDIR}" in key:
                    key = key.replace("${SRCDIR}", "./")
                flags.add("%s=%s" % (key, value))

        cgo_flags = ["-std", cgo_options["std"]] + list(flags)

        fname = "cgo_inline.c"
        main = """
    int main(void) {
    }
    """
        template = source + main
        files = [(fname, template)]

        # clang.TranslationUnit
        # PARSE_NONE = 0
        # PARSE_DETAILED_PROCESSING_RECORD = 1
        # PARSE_INCOMPLETE = 2
        # PARSE_PRECOMPILED_PREAMBLE = 4
        # PARSE_CACHE_COMPLETION_RESULTS = 8
        # PARSE_SKIP_FUNCTION_BODIES = 64
        # PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION = 128
        options = 15

        # Index.parse(path, args=None, unsaved_files=None, options = 0)
        tu = index.parse(fname, cgo_flags, unsaved_files=files, options=options)

        # TranslationUnit.codeComplete(path, line, column, ...)
        cr = tu.codeComplete(
            fname,
            (line_count + 2),
            1,
            unsaved_files=files,
            include_macros=True,
            include_code_patterns=True,
            include_brief_comments=False,
        )

        if cgo_options["sort_algo"] == "priority":
            results = sorted(cr.results, key=cgo.get_priority)
        elif cgo_options["sort_algo"] == "alphabetical":
            results = sorted(cr.results, key=cgo.get_abbrevation)
        else:
            results = cr.results

        # Go string to C string
        #  The C string is allocated in the C heap using malloc.
        #  It is the caller's responsibility to arrange for it to be
        #  freed, such as by calling C.free (be sure to include stdlib.h
        #  if C.free is needed).
        #  func C.CString(string) *C.char
        #
        # Go []byte slice to C array
        #  The C array is allocated in the C heap using malloc.
        #  It is the caller's responsibility to arrange for it to be
        #  freed, such as by calling C.free (be sure to include stdlib.h
        #  if C.free is needed).
        #  func C.CBytes([]byte) unsafe.Pointer
        #
        # C string to Go string
        #  func C.GoString(*C.char) string
        #
        # C data with explicit length to Go string
        #  func C.GoStringN(*C.char, C.int) string
        #
        # C data with explicit length to Go []byte
        #  func C.GoBytes(unsafe.Pointer, C.int) []byte
        cache[source] = [
            {
                "word": "CString",
                "abbr": "CString(string) *C.char",
                "info": "CString(string) *C.char",
                "kind": "function",
                "dup": 1,
            },
            {
                "word": "CBytes",
                "abbr": "CBytes([]byte) unsafe.Pointer",
                "info": "CBytes([]byte) unsafe.Pointer",
                "kind": "function",
                "dup": 1,
            },
            {
                "word": "GoString",
                "abbr": "GoString(*C.char) string",
                "info": "GoString(*C.char) string",
                "kind": "function",
                "dup": 1,
            },
            {
                "word": "GoStringN",
                "abbr": "GoStringN(*C.char, C.int) string",
                "info": "GoStringN(*C.char, C.int) string",
                "kind": "function",
                "dup": 1,
            },
            {
                "word": "GoBytes",
                "abbr": "GoBytes(unsafe.Pointer, C.int) []byte",
                "info": "GoBytes(unsafe.Pointer, C.int) []byte",
                "kind": "function",
                "dup": 1,
            },
        ]
        cache[source] += list(map(cgo.parse_candidates, results))
        return cache[source]

    def get_priority(x):
        return x.string.priority

    def get_abbr(strings):
        for chunks in strings:
            if chunks.isKindTypedText():
                return chunks.spelling
        return ""

    def get_abbrevation(x):
        return cgo.get_abbr(x.string).lower()

    def find_binary_path(cmd):
        def is_exec(fpath):
            return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

        fpath, fname = os.path.split(cmd)
        if fpath:
            if is_exec(cmd):
                return cmd
        else:
            for path in os.environ["PATH"].split(os.pathsep):
                path = path.strip('"')
                binary = os.path.join(path, cmd)
                if is_exec(binary):
                    return binary
        return ""