/**
 *    Copyright (C) 2018-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.
 */

#pragma once

#include <iosfwd>
#include <string>

#include "mongo/base/string_data.h"
#include "mongo/db/jsobj.h"

namespace mongo {

namespace mutablebson {
class Element;
class Document;
}  // namespace mutablebson

/**
 * SafeNum holds and does arithmetic on a number in a safe way, handling overflow
 * and casting for the user. 32-bit integers will overflow into 64-bit integers. But
 * 64-bit integers will NOT overflow to doubles. Also, this class does NOT
 * downcast. Doubles will NOT overflow to decimal, but mixed type arithmetic with a decimal
 * will. This class should be as conservative as possible about upcasting, but
 * should never lose precision.
 *
 * This class does not throw any exceptions, so the user should call type() before
 * using a SafeNum to ensure that it is valid.  A SafeNum could be invalid
 * from creation (if, for example, a non-numeric BSONElement was passed to the
 * constructor) or due to overflow.  NAN is a valid value.
 *
 * Usage example:
 *
 *      SafeNum counter(doc["count"]);
 *
 *      SafeNum newValue = counter + 10;
 *      // check if valid
 *      if (newValue.type() == EOO) {
 *          return;
 *      }
 *      // append SafeNum to a BSONObj
 *      newValue.toBSON(fieldName, &bsonObjBuilder);
 *
 */
class SafeNum {
public:
    SafeNum();
    ~SafeNum();

    //
    // construction support
    //

    // Copy ctor and assignment are allowed.
    SafeNum(const SafeNum& rhs);
    SafeNum& operator=(const SafeNum& rhs);

    // These implicit conversions are allowed.
    SafeNum(const BSONElement& element);
    SafeNum(int32_t num);
    SafeNum(int64_t num);
    SafeNum(double num);
    SafeNum(Decimal128 num);

    // TODO: add Paul's mutablebson::Element ctor

    //
    // comparison support
    //

    /**
     * Returns true if the numeric quantity of 'rhs' and 'this' are the same. That is,
     * an int32(10), an int64(10), a double(10), and a decimal(10) are equivalent. An
     * EOO-typed safe num is equivalent only to another EOO-typed instance. Otherwise,
     * returns false.
     */
    bool isEquivalent(const SafeNum& rhs) const;
    bool operator==(const SafeNum& rhs) const;
    bool operator!=(const SafeNum& rhs) const;

    /**
     * Returns true if 'rsh' is equivalent to 'this' (see isEquivalent) _and_ both
     * types are exactly the same. An EOO-typed safe num is never identical to
     * anything else, even another EOO-typed instance. Otherwise, returns false.
     */
    bool isIdentical(const SafeNum& rhs) const;

    //
    // arithmetic support
    //

    /**
     * Sums the 'rhs' -- right-hand side -- safe num with this, taking care of
     * upconversions and overflow (see class header).
     */
    SafeNum operator+(const SafeNum& rhs) const;
    SafeNum& operator+=(const SafeNum& rhs);

    /**
     * Multiplies the 'rhs' -- right-hand side -- safe num with this, taking care of
     * upconversions and overflow (see class header).
     */
    SafeNum operator*(const SafeNum& rhs) const;
    SafeNum& operator*=(const SafeNum& rhs);

    //
    // logical operation support. Note that these operations are only supported for
    // integral types. Attempts to apply with either side holding a double or decimal
    // value will result in an EOO typed safenum.
    //

    // Bitwise 'and' support
    SafeNum bitAnd(const SafeNum& rhs) const;
    SafeNum operator&(const SafeNum& rhs) const;
    SafeNum& operator&=(const SafeNum& rhs);

    // Bitwise 'or' support
    SafeNum bitOr(const SafeNum& rhs) const;
    SafeNum operator|(const SafeNum& rhs) const;
    SafeNum& operator|=(const SafeNum& rhs);

    // Bitwise 'xor' support
    SafeNum bitXor(const SafeNum& rhs) const;
    SafeNum operator^(const SafeNum& rhs) const;
    SafeNum& operator^=(const SafeNum& rhs);

    //
    // output support
    //

    friend class mutablebson::Element;
    friend class mutablebson::Document;

    /**
     * Appends contents to given BSONObjBuilder.
     */
    void toBSON(StringData fieldName, BSONObjBuilder* bob) const;

    //
    // accessors
    //
    bool isValid() const;
    BSONType type() const;
    std::string debugString() const;

    //
    // Below exposed for testing purposes. Treat as private.
    //

    // Maximum integer that can be converted accuratelly into a double, assuming a
    // double precission IEEE 754 representation.
    // TODO use numeric_limits to make this portable
    static const int64_t maxIntInDouble = 9007199254740992LL;  // 2^53

private:
    // One of the following: NumberInt, NumberLong, NumberDouble, NumberDecimal, or EOO.
    BSONType _type;

    // Value of the safe num. Indeterminate if _type is EOO.
    union {
        int32_t int32Val;
        int64_t int64Val;
        double doubleVal;
        Decimal128::Value decimalVal;
    } _value;

    /**
     * Returns the sum of 'lhs' and 'rhs', taking into consideration their types. The
     * type of the result would upcast, if necessary and permitted. Otherwise, returns
     * an EOO-type instance.
     */
    static SafeNum addInternal(const SafeNum& lhs, const SafeNum& rhs);

    /**
     * Returns the product of 'lhs' and 'rhs', taking into consideration their types. The
     * type of the result would upcast, if necessary and permitted. Otherwise, returns an
     * EOO-type instance.
     */
    static SafeNum mulInternal(const SafeNum& lhs, const SafeNum& rhs);

    /** Returns the bitwise 'and' of lhs and rhs, taking into consideration their types. If
     *  the operation is invalid for the underlying types, returns an EOO instance.
     */
    static SafeNum andInternal(const SafeNum& lhs, const SafeNum& rhs);

    /** Returns the bitwise 'or' of lhs and rhs, taking into consideration their types. If
     *  the operation is invalid for the underlying types, returns an EOO instance.
     */
    static SafeNum orInternal(const SafeNum& lhs, const SafeNum& rhs);

    /** Returns the bitwise 'xor' of lhs and rhs, taking into consideration their types. If
     *  the operation is invalid for the underlying types, returns an EOO instance.
     */
    static SafeNum xorInternal(const SafeNum& lhs, const SafeNum& rhs);

    /**
     * Extracts the value of 'snum' in a int64_t format. It assumes 'snum' is an NumberInt
     * or a NumberLong.
     */
    static int64_t getInt64(const SafeNum& snum);

    /**
     * Extracts the value of 'snum' in a double format. It assumes 'snum' is a valid
     * SafeNum, i.e., that _type is not EOO.
     */
    static double getDouble(const SafeNum& snum);

    /**
     * Extracts the value of 'snum' in a Decimal128 format.  It assumes 'snum' is an
     * NumberInt, NumberDouble, or NumberLong.  Integral values are converted exactly.
     * NumberDouble is converted to 15 digits of precision, as defined in Decimal128.
     */
    static Decimal128 getDecimal(const SafeNum& snum);
};

// Convenience method for unittest code. Please use accessors otherwise.
std::ostream& operator<<(std::ostream& os, const SafeNum& snum);

inline SafeNum::SafeNum() : _type(EOO) {}

inline SafeNum::~SafeNum() {}

inline SafeNum::SafeNum(const SafeNum& rhs) : _type(rhs._type), _value(rhs._value) {}

inline SafeNum& SafeNum::operator=(const SafeNum& rhs) {
    _type = rhs._type;
    _value = rhs._value;
    return *this;
}

inline SafeNum::SafeNum(int32_t num) : _type(NumberInt) {
    _value.int32Val = num;
}

inline SafeNum::SafeNum(int64_t num) : _type(NumberLong) {
    _value.int64Val = num;
}

inline SafeNum::SafeNum(double num) : _type(NumberDouble) {
    _value.doubleVal = num;
}

inline SafeNum::SafeNum(Decimal128 num) : _type(NumberDecimal) {
    _value.decimalVal = num.getValue();
}

inline bool SafeNum::operator==(const SafeNum& rhs) const {
    return isEquivalent(rhs);
}

inline bool SafeNum::operator!=(const SafeNum& rhs) const {
    return !isEquivalent(rhs);
}

inline SafeNum SafeNum::operator+(const SafeNum& rhs) const {
    return addInternal(*this, rhs);
}

inline SafeNum& SafeNum::operator+=(const SafeNum& rhs) {
    return *this = addInternal(*this, rhs);
}

inline SafeNum SafeNum::operator*(const SafeNum& rhs) const {
    return mulInternal(*this, rhs);
}

inline SafeNum& SafeNum::operator*=(const SafeNum& rhs) {
    return *this = mulInternal(*this, rhs);
}

inline SafeNum SafeNum::bitAnd(const SafeNum& rhs) const {
    return andInternal(*this, rhs);
}

inline SafeNum SafeNum::operator&(const SafeNum& rhs) const {
    return bitAnd(rhs);
}

inline SafeNum& SafeNum::operator&=(const SafeNum& rhs) {
    return *this = bitAnd(rhs);
}

inline SafeNum SafeNum::bitOr(const SafeNum& rhs) const {
    return orInternal(*this, rhs);
}

inline SafeNum SafeNum::operator|(const SafeNum& rhs) const {
    return bitOr(rhs);
}

inline SafeNum& SafeNum::operator|=(const SafeNum& rhs) {
    return *this = bitOr(rhs);
}

inline SafeNum SafeNum::bitXor(const SafeNum& rhs) const {
    return xorInternal(*this, rhs);
}

inline SafeNum SafeNum::operator^(const SafeNum& rhs) const {
    return bitXor(rhs);
}

inline SafeNum& SafeNum::operator^=(const SafeNum& rhs) {
    return *this = bitXor(rhs);
}

inline bool SafeNum::isValid() const {
    return _type != EOO;
}

inline BSONType SafeNum::type() const {
    return _type;
}

}  // namespace mongo
