# Copyright 2017 Google LLC
#
# Licensed 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
#
#     https://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.

"""Generate C++ unit tests from JSON acceptance tests.

The Bigtable Client ReadRows acceptance tests are specified in the
file `read-rows-acceptance-test.json` from

  https://github.com/GoogleCloudPlatform/cloud-bigtable-client

This script reads the JSON test cases and writes out C++ source code
in googletest style to run as unit tests for the ReadRows response
parser.

Usage:
  curl -L \
   https://raw.githubusercontent.com/googleapis/conformance-tests/main/bigtable/v2/readrows.json |
  python3 ../tools/convert_acceptance_tests.py |
  clang-format >readrowsparser_acceptance_tests.inc

"""

import json
import sys
import base64

FILE_HEADER = (
    """
// AUTOGENERATED BY tools/convert_acceptance_tests.py;"""
    + " DO NOT "
    + """EDIT MANUALLY.
// ALL MANUAL CHANGES WILL BE OVERWRITTEN.
//
// Copyright 2019 Google LLC
//
// Licensed 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
//
//     https://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.
"""
)


def camel_case(test_name):
    """Convert a test name to CameCase"""
    words = "".join([c for c in test_name if c.isalpha() or c == " "]).split(" ")
    return "".join([w[:1].upper() + w[1:].lower() for w in words])


def test_case_is_success(test):
    """Determine if the test expects a successful parse"""
    if "results" not in test:
        return True
    for result in test["results"]:
        if "error" in result:
            return False
    return True


def get_chunks(test):
    """Return the chunks as C++ code"""
    if "chunks" not in test:
        return ""
    output = ""
    for chunk in test["chunks"]:
        output += '      R"chunk(\n'
        if "rowKey" in chunk:
            base64_string = chunk["rowKey"]
            rowkey_string = base64.b64decode(base64_string.encode("ascii")).decode(
                "ascii"
            )
            output += '          row_key: "' + rowkey_string + '"\n'
        if "familyName" in chunk:
            output += '          family_name: < value: "' + chunk["familyName"] + '">\n'
        if "qualifier" in chunk:
            base64_string = chunk["qualifier"]
            qualifier_string = base64.b64decode(base64_string.encode("ascii")).decode(
                "ascii"
            )
            output += '          qualifier: < value: "' + qualifier_string + '">\n'
        if "timestampMicros" in chunk:
            output += "          timestamp_micros: " + chunk["timestampMicros"] + "\n"
        if "labels" in chunk:
            output += '          labels: "' + str(*chunk["labels"]) + '"\n'
        if "value" in chunk:
            base64_string = chunk["value"]
            value_string = base64.b64decode(base64_string.encode("ascii")).decode(
                "ascii"
            )
            output += '          value: "' + value_string + '"\n'
        if "valueSize" in chunk:
            output += "          value_size: " + str(chunk["valueSize"]) + "\n"
        if "resetRow" in chunk:
            output += "          reset_row: " + str(chunk["resetRow"]).lower() + "\n"
        if "commitRow" in chunk:
            output += "          commit_row: " + str(chunk["commitRow"]).lower() + "\n"
        output += '        )chunk",\n'
    return output


def get_results(test):
    """Return the results as C++ code"""
    if "results" not in test:
        return ""
    output = ""
    for result in test["results"]:
        if "error" not in result:
            output += "\n"
            if "rowKey" in result:
                output += '      "rk: ' + result["rowKey"] + '\\n"\n'
            if "familyName" in result:
                output += '      "fm: ' + result["familyName"] + '\\n"\n'
            if "qualifier" in result:
                output += '      "qual: ' + result["qualifier"] + '\\n"\n'
            if "timestampMicros" in result:
                output += '      "ts: ' + str(result["timestampMicros"]) + '\\n"\n'
            else:
                output += '      "ts: ' + str(0) + '\\n"\n'
            if "value" in result:
                output += '      "value: ' + result["value"] + '\\n"\n'
            else:
                output += '      "value: ' + '\\n"\n'
            if "label" in result:
                output += '      "label: ' + result["label"] + '\\n",\n'
            else:
                output += '      "label: ' + '\\n",\n'
    return output


def print_test(test):
    """Prints a single test as a C++ Google Test"""
    output = '// Test name: "' + test["description"] + '"\n'
    output += "TEST_F(AcceptanceTest, " + camel_case(test["description"]) + ") {\n"

    output += "  std::vector<std::string> chunk_strings = {\n"
    chunks = get_chunks(test)
    output += chunks
    if chunks[-1] == "\n":
        output += "  "
    output += "  };\n"
    output += "\n"

    output += "  auto chunks = ConvertChunks(std::move(chunk_strings));\n"
    output += "  ASSERT_FALSE(chunks.empty());\n\n"

    if test_case_is_success(test):
        output += "EXPECT_THAT(FeedChunks(chunks), IsOk());\n\n"
    else:
        output += "EXPECT_THAT(FeedChunks(chunks), Not(IsOk()));\n\n"

    output += "  std::vector<std::string> expected_cells = {"
    results = get_results(test)
    output += results
    if results.endswith("\n"):
        output += "  "
    output += "};\n"

    output += "  EXPECT_EQ(expected_cells, ExtractCells());\n"
    output += "}\n"
    return output


def main():
    """Convert the conformance tests in Protobuf JSON to C++"""
    test_definitions = json.loads(sys.stdin.read())

    print(FILE_HEADER.lstrip())
    for test in test_definitions["readRowsTests"]:
        print(print_test(test))


if __name__ == "__main__":
    main()
