/**
 *    Copyright (C) 2017 MongoDB Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    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 GNU Affero General 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/platform/basic.h"

#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/stdx/unordered_set.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/uuid.h"

namespace mongo {
namespace {

TEST(UUIDTest, UUIDCollisionTest) {
    // Generate some UUIDs and check that they do not collide.
    // NOTE: if this test fails, it is not necessarily a bug. However, if it
    // begins to fail often and on specific platforms, we should investigate
    // the quality of our entropy on those systems.
    stdx::unordered_set<UUID, UUID::Hash> uuids;
    for (int i = 0; i < 10000; i++) {
        ASSERT(uuids.emplace(UUID::gen()).second);
    }
}

TEST(UUIDTest, isUUIDStringTest) {
    // Several valid strings
    ASSERT(UUID::isUUIDString("00000000-0000-4000-8000-000000000000"));
    ASSERT(UUID::isUUIDString("01234567-9abc-4def-9012-3456789abcde"));
    ASSERT(UUID::isUUIDString("dddddddd-eeee-4fff-aaaa-bbbbbbbbbbbb"));
    ASSERT(UUID::isUUIDString("A9A9A9A9-BEDF-4DD9-B001-222345716283"));

    // No version or variant set
    ASSERT(UUID::isUUIDString("00000000-0000-0000-0000-000000000000"));

    // Mixed casing is weird, but technically legal
    ASSERT(UUID::isUUIDString("abcdefAB-CDEF-4000-AaAa-FDFfdffd9991"));

    // Wrong number of Hyphens
    ASSERT(!UUID::isUUIDString("00000000-0000-4000-8000-0000000000-00"));
    ASSERT(!UUID::isUUIDString("000000000000-4000-8000-000000000000"));
    ASSERT(!UUID::isUUIDString("00000000000040008000000000000000"));

    // Hyphens in the wrong places
    ASSERT(!UUID::isUUIDString("dddddd-ddeeee-4fff-aaaa-bbbbbbbbbbbb"));
    ASSERT(!UUID::isUUIDString("ddddddd-deeee-4fff-aaaa-bbbbbbbbbbbb"));
    ASSERT(!UUID::isUUIDString("d-d-d-dddddeeee4fffaaaa-bbbbbbbbbbbb"));

    // Illegal characters
    ASSERT(!UUID::isUUIDString("samsamsa-sams-4sam-8sam-samsamsamsam"));

    // Too short
    ASSERT(!UUID::isUUIDString("A9A9A9A9-BEDF-4DD9-B001"));
    ASSERT(!UUID::isUUIDString("dddddddd-eeee-4fff-aaaa-bbbbbbbbbbb"));

    // Too long
    ASSERT(!UUID::isUUIDString("01234567-9abc-4def-9012-3456789abcdef"));
    ASSERT(!UUID::isUUIDString("0123004567-9abc-4def-9012-3456789abcdef0000"));
}

TEST(UUIDTest, toAndFromString) {
    // String -> UUID -> string
    auto s1 = "00000000-0000-4000-8000-000000000000";
    auto uuid1Res = UUID::parse(s1);
    ASSERT_OK(uuid1Res);
    auto uuid1 = uuid1Res.getValue();
    ASSERT(UUID::isUUIDString(s1));
    ASSERT(UUID::isUUIDString(uuid1.toString()));
    ASSERT_EQUALS(uuid1.toString(), s1);

    // UUID -> string -> UUID
    auto uuid2 = UUID::gen();
    auto s2 = uuid2.toString();
    ASSERT(UUID::isUUIDString(s2));

    auto uuid2FromStringRes = UUID::parse(s2);
    ASSERT_OK(uuid2FromStringRes);
    auto uuid2FromString = uuid2FromStringRes.getValue();
    ASSERT_EQUALS(uuid2FromString, uuid2);
    ASSERT_EQUALS(uuid2FromString.toString(), s2);

    // Two UUIDs constructed from the same string are equal
    auto s3 = "01234567-9abc-4def-9012-3456789abcde";
    auto uuid3Res = UUID::parse(s3);
    auto uuid3AgainRes = UUID::parse(s3);
    ASSERT_OK(uuid3Res);
    ASSERT_OK(uuid3AgainRes);
    ASSERT_EQUALS(uuid3Res.getValue(), uuid3AgainRes.getValue());

    // Two UUIDs constructed from differently cased string are equal
    auto sLower = "00000000-aaaa-4000-8000-000000000000";
    auto sUpper = "00000000-AAAA-4000-8000-000000000000";
    auto uuidLowerRes = UUID::parse(sLower);
    auto uuidUpperRes = UUID::parse(sUpper);
    ASSERT_OK(uuidLowerRes);
    ASSERT_OK(uuidUpperRes);
    auto uuidLower = uuidLowerRes.getValue();
    auto uuidUpper = uuidUpperRes.getValue();
    ASSERT_EQUALS(uuidLower, uuidUpper);

    // Casing is not preserved on round trip, both become lowercase
    ASSERT_EQUALS(uuidLower.toString(), uuidUpper.toString());
    ASSERT_EQUALS(uuidLower.toString(), sLower);
    ASSERT_EQUALS(uuidUpper.toString(), sLower);
    ASSERT_NOT_EQUALS(uuidUpper.toString(), sUpper);

    // UUIDs constructed from different strings are not equal
    auto s4 = "01234567-9abc-4def-9012-3456789abcde";
    auto s5 = "01234567-0000-4def-9012-3456789abcde";
    ASSERT_NOT_EQUALS(UUID::parse(s4).getValue(), UUID::parse(s5).getValue());

    // UUIDs cannot be constructed from invalid strings
    ASSERT_EQUALS(ErrorCodes::InvalidUUID, UUID::parse("00000000000040008000000000000000"));
    ASSERT_EQUALS(ErrorCodes::InvalidUUID, UUID::parse("d-d-d-dddddeeee4fffaaaa-bbbbbbbbbbbb"));
    ASSERT_EQUALS(ErrorCodes::InvalidUUID, UUID::parse("samsamsa-sams-4sam-8sam-samsamsamsam"));
}

TEST(UUIDTest, toAndFromCDR) {
    UUID uuid = UUID::gen();
    ConstDataRange cdr = uuid.toCDR();
    UUID roundtripped = UUID::fromCDR(cdr);
    ASSERT_EQUALS(roundtripped, uuid);
}

TEST(UUIDTest, toAndFromBSON) {
    // UUID -> BSON -> UUID
    UUID uuid = UUID::gen();
    auto uuidBSON = uuid.toBSON();
    auto uuid2Res = UUID::parse(uuidBSON.getField("uuid"));
    ASSERT_OK(uuid2Res);
    auto uuid2 = uuid2Res.getValue();
    ASSERT_EQUALS(uuid, uuid2Res);

    // BSON -> UUID -> BSON
    uint8_t uuidBytes[] = {0, 0, 0, 0, 0, 0, 0x40, 0, 0x80, 0, 0, 0, 0, 0, 0, 0};
    auto bson = BSON("uuid" << BSONBinData(uuidBytes, 16, newUUID));
    auto uuidFromBSON = UUID::parse(bson.getField("uuid"));
    ASSERT_OK(uuidFromBSON);

    auto uuidBSON2 = uuid2.toBSON();
    ASSERT_EQUALS(uuidBSON.woCompare(uuidBSON2), 0);

    // UUIDs cannot be constructed from invalid BSON elements
    auto bson2 = BSON("uuid"
                      << "sam");
    ASSERT_EQUALS(ErrorCodes::InvalidUUID, UUID::parse(bson2.getField("uuid")));
    auto bson3 = BSON("uuid"
                      << "dddddddd-eeee-4fff-aaaa-bbbbbbbbbbbb");
    ASSERT_EQUALS(ErrorCodes::InvalidUUID, UUID::parse(bson3.getField("uuid")));
    auto bson4 = BSON("uuid" << 14);
    ASSERT_EQUALS(ErrorCodes::InvalidUUID, UUID::parse(bson4.getField("uuid")));
}

TEST(UUIDTest, toBSONUsingBSONMacro) {
    auto uuid = UUID::gen();
    auto bson = BSON("myuuid" << uuid);

    BSONObjBuilder bob;
    uuid.appendToBuilder(&bob, "myuuid");
    auto expectedBson = bob.obj();

    ASSERT_BSONOBJ_EQ(expectedBson, bson);
}

}  // namespace
}  // namespace mongo
