/**
 *    Copyright (C) 2019-present MongoDB, Inc.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the Server Side Public License, version 1,
 *    as published by MongoDB, Inc.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    Server Side Public License for more details.
 *
 *    You should have received a copy of the Server Side Public License
 *    along with this program. If not, see
 *    <http://www.mongodb.com/licensing/server-side-public-license>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the Server Side Public License in all respects for
 *    all of the code used other than as permitted herein. If you modify file(s)
 *    with this exception, you may extend this exception to your version of the
 *    file(s), but you are not obligated to do so. If you do not wish to do so,
 *    delete this exception statement from your version. If you delete this
 *    exception statement from all source files in the program, then also delete
 *    it in the license file.
 */

#include "mongo/shell/shell_utils.h"

#include "mongo/bson/json.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"

namespace mongo {
namespace {
using shell_utils::NormalizationOpts;
using shell_utils::NormalizationOptsSet;
using shell_utils::normalizeBSONObj;

const auto fullOpts = NormalizationOpts::kSortBSON | NormalizationOpts::kSortArrays |
    NormalizationOpts::kNormalizeNumerics;

TEST(ShellUtils, BalancedTest) {
    using shell_utils::isBalanced;
    struct {
        std::string in;
        bool isBalanced;
    } specs[] = {{"x = 5", true},
                 {"function(){}", true},
                 {"function(){\n}", true},
                 {"function(){", false},
                 {R"(x = "{";)", true},
                 {"// {", true},
                 {"// \n {", false},
                 {R"("//" {)", false},
                 {R"({x:/x\//})", true},
                 {R"({ \/// })", false},
                 {"x = 5 + y ", true},
                 {"x = ", false},
                 {"x = // hello", false},
                 {"x = 5 +", false},
                 {" x ++", true},
                 {"-- x", true},
                 {"a.", false},
                 {"a. ", false},
                 {"a.b", true},
                 // SERVER-5809 and related cases --
                 {R"(a = {s:"\""})", true},
                 {R"(db.test.save({s:"\""}))", true},
                 {R"(printjson(" \" "))", true},  //-- SERVER-8554
                 {R"(var a = "\\";)", true},
                 {R"(var a = ("\\") //")", true},
                 {R"(var a = ("\\") //\")", true},
                 {R"(var a = ("\\") //\")", true},
                 {R"(var a = ("\\") //)", true},
                 {R"(var a = ("\\"))", true},
                 {R"(var a = ("\\\""))", true},
                 {R"(var a = ("\\" //")", false},
                 {R"(var a = ("\\" //)", false},
                 {R"(var a = ("\\")", false}};
    for (const auto& spec : specs) {
        ASSERT_EQUALS(isBalanced(spec.in), spec.isBalanced);
    }
}

// Numeric arrays.
TEST(NormalizeBSONObj, NumericArray) {
    BSONObj bson = fromjson("{a: [4, 3, 2, 1, 0]}");
    std::string expected =
        "{ a: [ 0E-6176, 1.000000000000000000000000000000000, 2.000000000000000000000000000000000, "
        "3.000000000000000000000000000000000, 4.000000000000000000000000000000000 ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, NumericNestedArray) {
    BSONObj bson = fromjson("{a: [4, [1, 2, 3], 2, 1, 0]}");
    std::string expected =
        "{ a: [ 0E-6176, 1.000000000000000000000000000000000, 2.000000000000000000000000000000000, "
        "4.000000000000000000000000000000000, [ 1.000000000000000000000000000000000, "
        "2.000000000000000000000000000000000, 3.000000000000000000000000000000000 ] ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, NumericNestedArrayDiffFieldOrder) {
    BSONObj bson = fromjson("{b: [2, 1], a: [4, [1, 2, 3], 2, 1, 0]}");
    std::string expected =
        "{ a: [ 0E-6176, 1.000000000000000000000000000000000, 2.000000000000000000000000000000000, "
        "4.000000000000000000000000000000000, [ 1.000000000000000000000000000000000, "
        "2.000000000000000000000000000000000, 3.000000000000000000000000000000000 ] ], b: [ "
        "1.000000000000000000000000000000000, 2.000000000000000000000000000000000 ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, NumericNestedArrayDiffFields) {
    BSONObj a = fromjson("{a: [0, 1, 2, [3, 2, 1], 4], b: [1, 2]}");
    BSONObj b = fromjson("{a: [2, 1], b: [4, [1, 2, 3], 2, 1, 0]}");
    ASSERT_NOT_EQUALS(normalizeBSONObj(a, fullOpts).toString(),
                      normalizeBSONObj(b, fullOpts).toString());
}

TEST(NormalizeBSONObj, NumericArrayWithUnsortedNestedArrays) {
    BSONObj bson = fromjson("{a: [[9, 2, 3], [8, 0, 1], [7, 5, 6]]}");
    std::string expected =
        "{ a: [ [ 0E-6176, 1.000000000000000000000000000000000, "
        "8.000000000000000000000000000000000 ], [ 2.000000000000000000000000000000000, "
        "3.000000000000000000000000000000000, 9.000000000000000000000000000000000 ], [ "
        "5.000000000000000000000000000000000, 6.000000000000000000000000000000000, "
        "7.000000000000000000000000000000000 ] ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Mixed type numeric objects and arrays.
TEST(NormalizeBSONObj, NumericArrayDiffTypes) {
    BSONObj bson = fromjson("{a: [NumberDecimal('1.0'), NumberLong(1), NumberInt(1), 1.0]}");
    std::string expected =
        "{ a: [ 1.000000000000000000000000000000000, 1.000000000000000000000000000000000, "
        "1.000000000000000000000000000000000, 1.000000000000000000000000000000000 ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, NumericArrayDiffTypesDiffVals) {
    BSONObj bson = fromjson("{a: [NumberDecimal('1.00010'), 1.0, 1, 1.01]}");
    std::string expected =
        "{ a: [ 1.000000000000000000000000000000000, 1.000000000000000000000000000000000, "
        "1.000100000000000000000000000000000, 1.010000000000000000000000000000000 ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, NumericArrayDiffVals) {
    BSONObj a = fromjson("{a: [1.01, NumberDecimal('1.0001'), 1.0002]}");
    BSONObj b = fromjson("{a: [NumberDecimal('1.00012'), 1.0002, 1.01]}");
    ASSERT_NOT_EQUALS(normalizeBSONObj(a, fullOpts).toString(),
                      normalizeBSONObj(b, fullOpts).toString());
}

TEST(NormalizeBSONObj, NumericObjDiffTypes) {
    BSONObj bson = fromjson("{a: NumberDecimal('1.0'), b: NumberInt(1)}");
    std::string expected =
        "{ a: 1.000000000000000000000000000000000, b: 1.000000000000000000000000000000000 }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// String arrays.
TEST(NormalizeBSONObj, StringArray) {
    BSONObj bson = fromjson("{a: ['B', 'b', 'A', 'ab', 'a']}");
    std::string expected = "{ a: [ \"A\", \"B\", \"a\", \"ab\", \"b\" ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, StringArrayNested) {
    BSONObj bson = fromjson("{a: ['b', ['a', 'ab', 'A'], 'A', 'ab', 'a']}");
    std::string expected = "{ a: [ \"A\", \"a\", \"ab\", \"b\", [ \"A\", \"a\", \"ab\" ] ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Object arrays.
TEST(NormalizeBSONObj, ObjectArray) {
    BSONObj bson = fromjson("{a: [{d: 1, c: 1}, {b: 1}, {a: 1}]}");
    std::string expected =
        "{ a: [ { a: 1.000000000000000000000000000000000 }, { b: "
        "1.000000000000000000000000000000000 }, { c: 1.000000000000000000000000000000000, d: "
        "1.000000000000000000000000000000000 } ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, ObjectArrayNested) {
    BSONObj bson = fromjson("{a: [{b: 1}, {d: [{b: 1}, {d: 1, c: 1}, {a: 1}], c: 1}, {a: 1}]}]}");
    std::string expected =
        "{ a: [ { a: 1.000000000000000000000000000000000 }, { b: "
        "1.000000000000000000000000000000000 }, { c: 1.000000000000000000000000000000000, d: [ { "
        "a: 1.000000000000000000000000000000000 }, { b: 1.000000000000000000000000000000000 }, { "
        "c: 1.000000000000000000000000000000000, d: 1.000000000000000000000000000000000 } ] } ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, ObjectArrayWithNestedArrays) {
    BSONObj bson = fromjson("{a: {d: [{d: [5, 7, 6]}, {c: [[10, 1], [4, 5], [3, 7], 1]}]}}");
    std::string expected =
        "{ a: { d: [ { c: [ 1.000000000000000000000000000000000, [ "
        "1.000000000000000000000000000000000, 10.00000000000000000000000000000000 ], [ "
        "3.000000000000000000000000000000000, 7.000000000000000000000000000000000 ], [ "
        "4.000000000000000000000000000000000, 5.000000000000000000000000000000000 ] ] }, { d: [ "
        "5.000000000000000000000000000000000, 6.000000000000000000000000000000000, "
        "7.000000000000000000000000000000000 ] } ] } }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, ObjectArrayWithDiffNestedArrays) {
    BSONObj a = fromjson("{a: {d: [{c: [1, [7, 3], [5, 4], [1, 10]]}, {d: [7, 6, 5]}]}}");
    BSONObj b = fromjson("{a: {d: [{d: [5, 7, 6]}, {c: [[10, 4], [1, 5], [3, 7], 1]}]}}");
    ASSERT_NOT_EQUALS(normalizeBSONObj(a, fullOpts).toString(),
                      normalizeBSONObj(b, fullOpts).toString());
}

TEST(NormalizeBSONObj, ObjectArrayWithDiffFields) {
    BSONObj bson = fromjson("{a: {d: [{c: [5, 7, 6]}, {c: [[10, 1], [4, 5], [3, 7], 1]}]}}");
    std::string expected =
        "{ a: { d: [ { c: [ 1.000000000000000000000000000000000, [ "
        "1.000000000000000000000000000000000, 10.00000000000000000000000000000000 ], [ "
        "3.000000000000000000000000000000000, 7.000000000000000000000000000000000 ], [ "
        "4.000000000000000000000000000000000, 5.000000000000000000000000000000000 ] ] }, { c: [ "
        "5.000000000000000000000000000000000, 6.000000000000000000000000000000000, "
        "7.000000000000000000000000000000000 ] } ] } }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// BinData arrays.
TEST(NormalizeBSONObj, BinDataArray) {
    BSONBinData a = BSONBinData("abcdabcdabcdabcd", 16, BinDataType::newUUID);
    BSONBinData b = BSONBinData("abcdabcdabcdabce", 16, BinDataType::newUUID);

    BSONObj bson = BSON("a" << BSON_ARRAY(b << a));
    std::string expected =
        "{ a: [ UUID(\"61626364-6162-6364-6162-636461626364\"), "
        "UUID(\"61626364-6162-6364-6162-636461626365\") ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// ObjectId arrays.
TEST(NormalizeBSONObj, ObjectIDArray) {
    OID a = OID("112233445566778899AABBCC");
    OID b = OID("112233445566778899AABBCB");

    BSONObj bson = BSON("a" << BSON_ARRAY(a << b));
    std::string expected =
        "{ a: [ ObjectId('112233445566778899aabbcb'), ObjectId('112233445566778899aabbcc') ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Boolean arrays.
TEST(NormalizeBSONObj, BooleanArray) {
    BSONObj bson = fromjson("{a: [true, false, false]}");
    std::string expected = "{ a: [ false, false, true ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, BooleanNestedArray) {
    BSONObj bson = fromjson("{a: [true, false, [false, true]]}");
    std::string expected = "{ a: [ [ false, true ], false, true ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Date arrays.
TEST(NormalizeBSONObj, DateArray) {
    BSONObj bson = fromjson(
        "{a: [new Date(1351242000002), new Date(1351242000000), new Date(1351242000001)]}");
    std::string expected =
        "{ a: [ new Date(1351242000000), new Date(1351242000001), new Date(1351242000002) ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Timestamp arrays.
TEST(NormalizeBSONObj, TimestampArray) {
    BSONObj bson = BSON("a" << BSON_ARRAY(Timestamp(3, 4) << Timestamp(2, 3) << Timestamp(1, 2)));
    std::string expected = "{ a: [ Timestamp(1, 2), Timestamp(2, 3), Timestamp(3, 4) ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Regex arrays.
TEST(NormalizeBSONObj, RegexArray) {
    BSONObj bson = BSON(
        "a" << BSON_ARRAY(BSONRegEx("reg.ex") << BSONRegEx("a*.conn") << BSONRegEx("/regex/")));
    std::string expected = "{ a: [ //regex//, /a*.conn/, /reg.ex/ ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Mixed type arrays.
TEST(NormalizeBSONObj, MixedTypeArray) {
    BSONObj bson = fromjson("{a: [[3, 2], true, 0, {b: 5}, 'a']}");
    std::string expected =
        "{ a: [ 0E-6176, \"a\", { b: 5.000000000000000000000000000000000 }, [ "
        "2.000000000000000000000000000000000, 3.000000000000000000000000000000000 ], true ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

TEST(NormalizeBSONObj, MixedTypeArrayNested) {
    BSONObj bson = fromjson("{a: [true, ['a', [0], true], 'a', 0]}");
    std::string expected = "{ a: [ 0E-6176, \"a\", [ \"a\", [ 0E-6176 ], true ], true ] }";
    ASSERT_EQ(normalizeBSONObj(bson, fullOpts).toString(), expected);
}

// Test that NormalizationOpts are correctly parsed.
TEST(NormalizationOpts, Default) {
    BSONObj bson = fromjson("{a: {c: 1, b: [2, 1]}}");
    NormalizationOptsSet opts = NormalizationOpts::kResults;

    std::string expected = "{ a: { c: 1, b: [ 2, 1 ] } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, NormalizeNumerics) {
    BSONObj bson = fromjson("{a: {c: 1, b: [2, 1]}}");
    NormalizationOptsSet opts = NormalizationOpts::kNormalizeNumerics;

    std::string expected =
        "{ a: { c: 1.000000000000000000000000000000000, b: [ 2.000000000000000000000000000000000, "
        "1.000000000000000000000000000000000 ] } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, SortBSON) {
    BSONObj bson = fromjson("{a: {c: 1, b: [2, 1]}}");
    NormalizationOptsSet opts = NormalizationOpts::kSortBSON;

    std::string expected = "{ a: { b: [ 2, 1 ], c: 1 } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, ConflateNullAndMissing) {
    BSONObj bson = fromjson("{a: null}");
    NormalizationOptsSet opts = NormalizationOpts::kConflateNullAndMissing;

    std::string expected = "{}";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, ConflateNullAndMissingNested) {
    BSONObj bson = fromjson("{a: {b: null}}");
    NormalizationOptsSet opts = NormalizationOpts::kConflateNullAndMissing;

    std::string expected = "{ a: {} }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, ConflateNullAndMissingNestedObjAndArray) {
    BSONObj bson = fromjson("{a: {c: null, b: [undefined, null, 5]}}");
    NormalizationOptsSet opts = NormalizationOpts::kConflateNullAndMissing;

    std::string expected = "{ a: { b: [ 5 ] } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, ConflateNullAndMissingNestedObjInArray) {
    BSONObj bson = fromjson("{a: {b: [null, undefined, {d: null}]}}");
    NormalizationOptsSet opts = NormalizationOpts::kConflateNullAndMissing;

    std::string expected = "{ a: { b: [ {} ] } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, SortBSONSortArrays) {
    BSONObj bson = fromjson("{a: {c: 1, b: [2, 1]}}");
    NormalizationOptsSet opts = NormalizationOpts::kSortBSON | NormalizationOpts::kSortArrays;

    std::string expected = "{ a: { b: [ 1, 2 ], c: 1 } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, NormalizeNumericsSortBSON) {
    BSONObj bson = fromjson("{a: {c: 1, b: [2, 1]}}");
    NormalizationOptsSet opts =
        NormalizationOpts::kSortBSON | NormalizationOpts::kNormalizeNumerics;

    std::string expected =
        "{ a: { b: [ 2.000000000000000000000000000000000, 1.000000000000000000000000000000000 ], "
        "c: 1.000000000000000000000000000000000 } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, NormalizeFull) {
    BSONObj bson = fromjson("{a: {c: 1, b: [2, null, 1, undefined]}}");
    NormalizationOptsSet opts = NormalizationOpts::kNormalizeNumerics |
        NormalizationOpts::kSortBSON | NormalizationOpts::kSortArrays;

    std::string expected =
        "{ a: { b: [ undefined, null, 1.000000000000000000000000000000000, "
        "2.000000000000000000000000000000000 ], "
        "c: 1.000000000000000000000000000000000 } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}

TEST(NormalizationOpts, NormalizeFullConflateNullAndMissing) {
    BSONObj bson = fromjson("{a: {c: 1, b: [2, null, 1, undefined]}}");
    NormalizationOptsSet opts = NormalizationOpts::kNormalizeNumerics |
        NormalizationOpts::kSortBSON | NormalizationOpts::kSortArrays |
        NormalizationOpts::kConflateNullAndMissing;

    std::string expected =
        "{ a: { b: [ 1.000000000000000000000000000000000, 2.000000000000000000000000000000000 ], "
        "c: 1.000000000000000000000000000000000 } }";
    ASSERT_EQ(normalizeBSONObj(bson, opts).toString(), expected);
}
}  // namespace
}  // namespace mongo
