#!/usr/bin/python3

import json
import os
import pprint
import re
import shutil
import subprocess
import time
import unittest

ccls = os.getenv("CCLS", "ccls")
path = os.path.dirname(os.path.realpath(__file__))
version = re.findall(
    r"ccls version ([\S]+)",
    subprocess.check_output([ccls, "--version"]).decode().strip(),
)[0]

tests = [
    (
        {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {"rootUri": "file://" + path},
        },
        {
            "jsonrpc": "2.0",
            "id": 1,
            "result": {
                "capabilities": {
                    "textDocumentSync": {
                        "openClose": True,
                        "change": 2,
                        "willSave": False,
                        "willSaveWaitUntil": False,
                        "save": {"includeText": False},
                    },
                    "hoverProvider": True,
                    "completionProvider": {
                        "resolveProvider": False,
                        "triggerCharacters": [".", ":", ">", "#", "<", '"', "/"],
                    },
                    "signatureHelpProvider": {"triggerCharacters": ["(", ","]},
                    "declarationProvider": True,
                    "definitionProvider": True,
                    "implementationProvider": True,
                    "typeDefinitionProvider": True,
                    "referencesProvider": True,
                    "documentHighlightProvider": True,
                    "documentSymbolProvider": True,
                    "workspaceSymbolProvider": True,
                    "codeActionProvider": {"codeActionKinds": ["quickfix"]},
                    "codeLensProvider": {"resolveProvider": False},
                    "documentFormattingProvider": True,
                    "documentRangeFormattingProvider": True,
                    "documentOnTypeFormattingProvider": {
                        "firstTriggerCharacter": "}",
                        "moreTriggerCharacter": [],
                    },
                    "renameProvider": True,
                    "documentLinkProvider": {"resolveProvider": True},
                    "foldingRangeProvider": True,
                    "executeCommandProvider": {"commands": ["ccls.xref"]},
                    "workspace": {
                        "workspaceFolders": {
                            "supported": True,
                            "changeNotifications": True,
                        }
                    },
                },
                "serverInfo": {"name": "ccls", "version": version},
            },
        },
    ),
    (
        {"jsonrpc": "2.0", "id": 1, "method": "$ccls/info"},
        {
            "jsonrpc": "2.0",
            "id": 1,
            "result": {
                "db": {"files": 1, "funcs": 0, "types": 1, "vars": 1},
                "pipeline": {"lastIdle": 0, "completed": 1, "enqueued": 1},
                "project": {"entries": 1},
            },
        },
    ),
    (
        {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "workspace/symbol",
            "params": {"query": "test"},
        },
        {
            "id": 1,
            "jsonrpc": "2.0",
            "result": [
                {
                    "kind": 13,
                    "location": {
                        "range": {
                            "end": {"character": 12, "line": 0},
                            "start": {"character": 4, "line": 0},
                        },
                        "uri": "file://" + path + "/sample.c",
                    },
                    "name": "int test_var",
                }
            ],
        },
    ),
]


def encode(msg):
    msg_str = json.dumps(msg)
    rpc_str = "Content-Length: %d\r\n\r\n%s" % (len(msg_str), msg_str)
    return rpc_str.encode()


def read(f):
    size = int(re.findall(r"Content-Length: (\d+)", f.readline().decode())[0])
    f.readline()
    return json.loads(f.read(size).decode())


class TestLSP(unittest.TestCase):
    maxDiff = None
    timeout = 100

    def tearDown(self):
        shutil.rmtree(path + "/.ccls-cache", ignore_errors=True)

    def test_index(self):
        subprocess.run(
            [ccls, "-index=.", "-v=-1"], stderr=subprocess.STDOUT
        ).check_returncode()

    def test_response(self):
        with subprocess.Popen(
            [ccls, "-v=-1"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
        ) as proc:

            def one(case):
                for i in range(self.timeout):
                    proc.stdin.write(encode(case[0]))
                    proc.stdin.flush()
                    got = read(proc.stdout)
                    try:
                        self.assertEqual(case[1], got)
                        break
                    except:
                        if i < self.timeout - 1:
                            time.sleep(0.1)
                        else:
                            raise

            print()
            for tt in tests:
                msg = tt[0]["method"]
                with self.subTest(msg):
                    print("Running", msg)
                    one(tt)
            proc.stdin.close()


if __name__ == "__main__":
    unittest.main(verbosity=2)
