// Copyright (c) 2015 Baidu, Inc.

#ifndef BUTIL_STATUS_H
#define BUTIL_STATUS_H

#include <stdarg.h>                       // va_list
#include <stdlib.h>                       // free
#include <string>                         // std::string
#include <ostream>                        // std::ostream
#include "butil/strings/string_piece.h"

namespace butil {

// A Status encapsulates the result of an operation. It may indicate success,
// or it may indicate an error with an associated error message. It's suitable
// for passing status of functions with richer information than just error_code
// in exception-forbidden code. This utility is inspired by leveldb::Status.
//
// Multiple threads can invoke const methods on a Status without
// external synchronization, but if any of the threads may call a
// non-const method, all threads accessing the same Status must use
// external synchronization.
//
// Since failed status needs to allocate memory, you should be careful when
// failed status is frequent.

class Status {
public:
    struct State {
        int code;
        unsigned size;  // length of message string
        unsigned state_size;
        char message[0];
    };

    // Create a success status.
    Status() : _state(NULL) { }
    // Return a success status.
    static Status OK() { return Status(); }

    ~Status() { reset(); }

    // Create a failed status.
    // error_text is formatted from `fmt' and following arguments.
    Status(int code, const char* fmt, ...) 
        __attribute__ ((__format__ (__printf__, 3, 4)))
        : _state(NULL) {
        va_list ap;
        va_start(ap, fmt);
        set_errorv(code, fmt, ap);
        va_end(ap);
    }
    Status(int code, const butil::StringPiece& error_msg) : _state(NULL) {
        set_error(code, error_msg);
    }

    // Copy the specified status. Internal fields are deeply copied.
    Status(const Status& s);
    void operator=(const Status& s);

    // Reset this status to be OK.
    void reset();
    
    // Reset this status to be failed.
    // Returns 0 on success, -1 otherwise and internal fields are not changed.
    int set_error(int code, const char* error_format, ...)
        __attribute__ ((__format__ (__printf__, 3, 4)));
    int set_error(int code, const butil::StringPiece& error_msg);
    int set_errorv(int code, const char* error_format, va_list args);

    // Returns true iff the status indicates success.
    bool ok() const { return (_state == NULL); }

    // Get the error code
    int error_code() const {
        return (_state == NULL) ? 0 : _state->code;
    }

    // Return a string representation of the status.
    // Returns "OK" for success.
    // NOTICE:
    //   * You can print a Status to std::ostream directly
    //   * if message contains '\0', error_cstr() will not be shown fully.
    const char* error_cstr() const {
        return (_state == NULL ? "OK" : _state->message);
    }
    butil::StringPiece error_data() const {
        return (_state == NULL ? butil::StringPiece("OK", 2) 
                : butil::StringPiece(_state->message, _state->size));
    }
    std::string error_str() const;

    void swap(butil::Status& other) { std::swap(_state, other._state); }

private:    
    // OK status has a NULL _state.  Otherwise, _state is a State object
    // converted from malloc().
    State* _state;

    static State* copy_state(const State* s);
};

inline Status::Status(const Status& s) {
    _state = (s._state == NULL) ? NULL : copy_state(s._state);
}

inline int Status::set_error(int code, const char* msg, ...) {
    va_list ap;
    va_start(ap, msg);
    const int rc = set_errorv(code, msg, ap);
    va_end(ap);
    return rc;
}

inline void Status::reset() {
    free(_state);
    _state = NULL;
}

inline void Status::operator=(const Status& s) {
    // The following condition catches both aliasing (when this == &s),
    // and the common case where both s and *this are ok.
    if (_state == s._state) {
        return;
    }
    if (s._state == NULL) {
        free(_state);
        _state = NULL;
    } else {
        set_error(s._state->code,
                  butil::StringPiece(s._state->message, s._state->size));
    }
}

inline std::ostream& operator<<(std::ostream& os, const Status& st) {
    // NOTE: don't use st.error_text() which is inaccurate if message has '\0'
    return os << st.error_data();
}

}  // namespace butil

#endif  // BUTIL_STATUS_H
