/*    Copyright 2012 10gen Inc.
 *
 *    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
 *
 *    http://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.
 */

#pragma once

#include <iosfwd>
#include <string>

#include "mongo/base/error_codes.h"
#include "mongo/client/export_macros.h"
#include "mongo/platform/atomic_word.h"

namespace mongo {

/**
 * Status represents an error state or the absence thereof.
 *
 * A Status uses the standardized error codes -- from file 'error_codes.h' -- to
 * determine an error's cause. It further clarifies the error with a textual
 * description. Optionally, a Status may also have an error location number, which
 * should be a unique, grep-able point in the code base (including assert numbers).
 *
 * Example usage:
 *
 *    Status sumAB(int a, int b, int* c) {
 *       if (overflowIfSum(a,b)) {
 *           return Status(ErrorCodes::ERROR_OVERFLOW, "overflow in sumAB", 16494);
 *       }
 *
 *       *c = a+b;
 *       return Status::OK();
 *   }
 *
 * TODO: expand base/error_codes.h to capture common errors in current code
 * TODO: generate base/error_codes.h out of a description file
 * TODO: check 'location' duplicates against assert numbers
 */
class MONGO_CLIENT_API Status {
public:
    // Short-hand for returning an OK status.
    static inline Status MONGO_CLIENT_FUNC OK();

    /**
     * Builds an error status given the error code, a textual description of what
     * caused the error, and a unique position in the where the error occurred
     * (similar to an assert number)
     */
    Status(ErrorCodes::Error code, const std::string& reason, int location = 0);
    Status(ErrorCodes::Error code, const char* reason, int location = 0);

    inline Status(const Status& other);
    inline Status& operator=(const Status& other);

#if __cplusplus >= 201103L
    inline Status(Status&& other) noexcept;
    inline Status& operator=(Status&& other) noexcept;
#endif  // __cplusplus >= 201103L

    inline ~Status();

    /**
     * Returns true if 'other's error code and location are equal/different to this
     * instance's. Otherwise returns false.
     */
    bool compare(const Status& other) const;
    bool operator==(const Status& other) const;
    bool operator!=(const Status& other) const;

    /**
     * Returns true if 'other's error code is equal/different to this instance's.
     * Otherwise returns false.
     */
    bool compareCode(const ErrorCodes::Error other) const;
    bool operator==(const ErrorCodes::Error other) const;
    bool operator!=(const ErrorCodes::Error other) const;

    //
    // accessors
    //

    inline bool isOK() const;

    inline ErrorCodes::Error code() const;

    inline std::string codeString() const;

    inline std::string reason() const;

    inline int location() const;

    std::string toString() const;

    //
    // Below interface used for testing code only.
    //

    inline AtomicUInt32::WordType refCount() const;

private:
    inline Status();

    struct ErrorInfo {
        AtomicUInt32 refs;             // reference counter
        const ErrorCodes::Error code;  // error code
        const std::string reason;      // description of error cause
        const int location;            // unique location of the triggering line in the code

        static ErrorInfo* create(ErrorCodes::Error code, const StringData& reason, int location);

        ErrorInfo(ErrorCodes::Error code, const StringData& reason, int location);
    };

    ErrorInfo* _error;

    /**
     * Increment/Decrement the reference counter inside an ErrorInfo
     *
     * @param error  ErrorInfo to be incremented
     */
    static inline void MONGO_CLIENT_FUNC ref(ErrorInfo* error);
    static inline void MONGO_CLIENT_FUNC unref(ErrorInfo* error);
};

MONGO_CLIENT_API inline bool MONGO_CLIENT_FUNC
operator==(const ErrorCodes::Error lhs, const Status& rhs);

MONGO_CLIENT_API inline bool MONGO_CLIENT_FUNC
operator!=(const ErrorCodes::Error lhs, const Status& rhs);

//
// Convenience method for unittest code. Please use accessors otherwise.
//

MONGO_CLIENT_API std::ostream& MONGO_CLIENT_FUNC operator<<(std::ostream& os, const Status& status);
MONGO_CLIENT_API std::ostream& MONGO_CLIENT_FUNC operator<<(std::ostream& os, ErrorCodes::Error);

}  // namespace mongo

#include "mongo/base/status-inl.h"
