/*
 * Copyright (c) 2018-2020 Atmosphère-NX
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stratosphere.hpp>

namespace ams::util {

    namespace {

        struct UuidImpl {
            util::BitPack32 data[4];

            using TimeLow               = util::BitPack32::Field<0,                           BITSIZEOF(u32), u32>;

            using TimeMid               = util::BitPack32::Field<0,                           BITSIZEOF(u16), u16>;
            using TimeHighAndVersion    = util::BitPack32::Field<TimeMid::Next,               BITSIZEOF(u16), u16>;
            using Version               = util::BitPack32::Field<TimeMid::Next + 12,                       4, u16>;

            static_assert(TimeHighAndVersion::Next == Version::Next);

            using ClockSeqHiAndReserved = util::BitPack32::Field<0,                           BITSIZEOF(u8),  u8>;
            using Reserved              = util::BitPack32::Field<6,                           2,              u8>;
            using ClockSeqLow           = util::BitPack32::Field<ClockSeqHiAndReserved::Next, BITSIZEOF(u8),  u8>;
            using NodeLow               = util::BitPack32::Field<ClockSeqLow::Next,           BITSIZEOF(u16), u16>;

            static_assert(ClockSeqHiAndReserved::Next == Reserved::Next);

            using NodeHigh              = util::BitPack32::Field<0,                           BITSIZEOF(u32), u32>;


            inline Uuid Convert() const {
                /* Convert the fields from native endian to big endian. */
                util::BitPack32 converted[4] = {util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}};

                converted[0].Set<TimeLow>(util::ConvertToBigEndian(this->data[0].Get<TimeLow>()));

                converted[1].Set<TimeMid>(util::ConvertToBigEndian(this->data[1].Get<TimeMid>()));
                converted[1].Set<TimeHighAndVersion>(util::ConvertToBigEndian(this->data[1].Get<TimeHighAndVersion>()));


                converted[2].Set<ClockSeqHiAndReserved>(util::ConvertToBigEndian(this->data[2].Get<ClockSeqHiAndReserved>()));
                converted[2].Set<ClockSeqLow>(util::ConvertToBigEndian(this->data[2].Get<ClockSeqLow>()));

                u64 node_lo = static_cast<u64>(this->data[2].Get<NodeLow>());
                u64 node_hi = static_cast<u64>(this->data[3].Get<NodeHigh>());
                u64 node    = util::ConvertToBigEndian48(static_cast<u64>((node_hi << BITSIZEOF(u16)) | (node_lo)));

                constexpr u64 NodeLoMask = (UINT64_C(1) << BITSIZEOF(u16)) - 1u;
                constexpr u64 NodeHiMask = (UINT64_C(1) << BITSIZEOF(u32)) - 1u;

                converted[2].Set<NodeLow>(static_cast<u16>(node & NodeLoMask));
                converted[3].Set<NodeHigh>(static_cast<u32>((node >> BITSIZEOF(u16)) & NodeHiMask));

                Uuid uuid;
                std::memcpy(uuid.data, converted, sizeof(uuid.data));
                return uuid;
            }
        };
        static_assert(sizeof(UuidImpl) == sizeof(Uuid));

        ALWAYS_INLINE Uuid GenerateUuidVersion4() {
            constexpr u16 Version  = 0x4;
            constexpr u8  Reserved = 0x1;

            /* Generate a random uuid. */
            UuidImpl uuid = {util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}};
            os::GenerateRandomBytes(uuid.data, sizeof(uuid.data));

            /* Set version and reserved. */
            uuid.data[1].Set<UuidImpl::Version>(Version);
            uuid.data[2].Set<UuidImpl::Reserved>(Reserved);

            /* Return the uuid. */
            return uuid.Convert();
        }

    }

    Uuid GenerateUuid() {
        return GenerateUuidVersion4();
    }

    Uuid GenerateUuidVersion5(const void *sha1_hash) {
        constexpr u16 Version  = 0x5;
        constexpr u8  Reserved = 0x1;

        /* Generate a uuid from a SHA1 hash. */
        UuidImpl uuid = {util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}};
        std::memcpy(uuid.data, sha1_hash, sizeof(uuid.data));

        /* Set version and reserved. */
        uuid.data[1].Set<UuidImpl::Version>(Version);
        uuid.data[2].Set<UuidImpl::Reserved>(Reserved);

        /* Return the uuid. */
        return uuid.Convert();
    }

}
