/**
 *    Copyright (C) 2013 10gen 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/s/write_ops/batched_command_response.h"

#include "mongo/db/field_parser.h"
#include "mongo/util/mongoutils/str.h"

namespace mongo {

    using mongoutils::str::stream;

    const BSONField<int> BatchedCommandResponse::ok("ok");
    const BSONField<int> BatchedCommandResponse::errCode("code", ErrorCodes::UnknownError);
    const BSONField<string> BatchedCommandResponse::errMessage("errmsg");
    const BSONField<long long> BatchedCommandResponse::n("n", 0);
    const BSONField<long long> BatchedCommandResponse::nModified("nModified", 0);
    const BSONField<std::vector<BatchedUpsertDetail*> >
        BatchedCommandResponse::upsertDetails("upserted");
    const BSONField<OpTime> BatchedCommandResponse::lastOp("lastOp");
    const BSONField<OID> BatchedCommandResponse::electionId("electionId");
    const BSONField<std::vector<WriteErrorDetail*> >
        BatchedCommandResponse::writeErrors("writeErrors");
    const BSONField<WCErrorDetail*> BatchedCommandResponse::writeConcernError("writeConcernError");

    BatchedCommandResponse::BatchedCommandResponse() {
        clear();
    }

    BatchedCommandResponse::~BatchedCommandResponse() {
        unsetErrDetails();
        unsetUpsertDetails();
    }

    bool BatchedCommandResponse::isValid(std::string* errMsg) const {
        std::string dummy;
        if (errMsg == NULL) {
            errMsg = &dummy;
        }

        // All the mandatory fields must be present.
        if (!_isOkSet) {
            *errMsg = stream() << "missing " << ok.name() << " field";
            return false;
        }

        return true;
    }

    BSONObj BatchedCommandResponse::toBSON() const {
        BSONObjBuilder builder;

        if (_isOkSet) builder.append(ok(), _ok);

        if (_isErrCodeSet) builder.append(errCode(), _errCode);

        if (_isErrMessageSet) builder.append(errMessage(), _errMessage);

        if (_isNModifiedSet) builder.appendNumber(nModified(), _nModified);
        if (_isNSet) builder.appendNumber(n(), _n);

        if (_upsertDetails.get()) {
            BSONArrayBuilder upsertedBuilder(builder.subarrayStart(upsertDetails()));
            for (std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
                 it != _upsertDetails->end();
                 ++it) {
                BSONObj upsertedDetailsDocument = (*it)->toBSON();
                upsertedBuilder.append(upsertedDetailsDocument);
            }
            upsertedBuilder.done();
        }

        if (_isLastOpSet) builder.append(lastOp(), _lastOp);
        if (_isElectionIdSet) builder.appendOID(electionId(), const_cast<OID*>(&_electionId));

        if (_writeErrorDetails.get()) {
            BSONArrayBuilder errDetailsBuilder(builder.subarrayStart(writeErrors()));
            for (std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
                 it != _writeErrorDetails->end();
                 ++it) {
                BSONObj errDetailsDocument = (*it)->toBSON();
                errDetailsBuilder.append(errDetailsDocument);
            }
            errDetailsBuilder.done();
        }

        if (_wcErrDetails.get()) {
            builder.append(writeConcernError(), _wcErrDetails->toBSON());
        }

        return builder.obj();
    }

    bool BatchedCommandResponse::parseBSON(const BSONObj& source, string* errMsg) {
        clear();

        std::string dummy;
        if (!errMsg) errMsg = &dummy;

        FieldParser::FieldState fieldState;
        fieldState = FieldParser::extractNumber(source, ok, &_ok, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) return false;
        _isOkSet = fieldState == FieldParser::FIELD_SET;

        fieldState = FieldParser::extract(source, errCode, &_errCode, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) return false;
        _isErrCodeSet = fieldState == FieldParser::FIELD_SET;

        fieldState = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) return false;
        _isErrMessageSet = fieldState == FieldParser::FIELD_SET;

        // We're using appendNumber on generation so we'll try a smaller type
        // (int) first and then fall back to the original type (long long).
        BSONField<int> fieldN(n());
        int tempN;
        fieldState = FieldParser::extract(source, fieldN, &tempN, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) {
            // try falling back to a larger type
            fieldState = FieldParser::extract(source, n, &_n, errMsg);
            if (fieldState == FieldParser::FIELD_INVALID) return false;
            _isNSet = fieldState == FieldParser::FIELD_SET;
        }
        else if (fieldState == FieldParser::FIELD_SET) {
            _isNSet = true;
            _n = tempN;
        }

        // We're using appendNumber on generation so we'll try a smaller type
        // (int) first and then fall back to the original type (long long).
        BSONField<int> fieldNModified(nModified());
        int intNModified;
        fieldState = FieldParser::extract(source, fieldNModified, &intNModified, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) {
            // try falling back to a larger type
            fieldState = FieldParser::extract(source, nModified, &_nModified, errMsg);
            if (fieldState == FieldParser::FIELD_INVALID) return false;
            _isNModifiedSet = fieldState == FieldParser::FIELD_SET;
        }
        else if (fieldState == FieldParser::FIELD_SET) {
            _isNModifiedSet = true;
            _nModified = intNModified;
        }

        std::vector<BatchedUpsertDetail*>* tempUpsertDetails = NULL;
        fieldState = FieldParser::extract( source, upsertDetails, &tempUpsertDetails, errMsg );
        if ( fieldState == FieldParser::FIELD_INVALID ) return false;
        if ( fieldState == FieldParser::FIELD_SET ) _upsertDetails.reset( tempUpsertDetails );

        fieldState = FieldParser::extract(source, lastOp, &_lastOp, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) return false;
        _isLastOpSet = fieldState == FieldParser::FIELD_SET;

        fieldState = FieldParser::extract(source, electionId, &_electionId, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) return false;
        _isElectionIdSet = fieldState == FieldParser::FIELD_SET;

        std::vector<WriteErrorDetail*>* tempErrDetails = NULL;
        fieldState = FieldParser::extract(source, writeErrors, &tempErrDetails, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) return false;
        if (fieldState == FieldParser::FIELD_SET) _writeErrorDetails.reset(tempErrDetails);

        WCErrorDetail* wcError = NULL;
        fieldState = FieldParser::extract(source, writeConcernError, &wcError, errMsg);
        if (fieldState == FieldParser::FIELD_INVALID) return false;
        if (fieldState == FieldParser::FIELD_SET) _wcErrDetails.reset(wcError);

        return true;
    }

    void BatchedCommandResponse::clear() {
        _ok = false;
        _isOkSet = false;

        _errCode = 0;
        _isErrCodeSet = false;

        _errMessage.clear();
        _isErrMessageSet = false;

        _nModified = 0;
        _isNModifiedSet = false;

        _n = 0;
        _isNSet = false;

        _singleUpserted = BSONObj();
        _isSingleUpsertedSet = false;

        if ( _upsertDetails.get() ) {
            for ( std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
                it != _upsertDetails->end(); ++it ) {
                delete *it;
            };
            _upsertDetails.reset();
        }

        _lastOp = OpTime();
        _isLastOpSet = false;

        _electionId = OID();
        _isElectionIdSet = false;

        if (_writeErrorDetails.get()) {
            for(std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
                it != _writeErrorDetails->end();
                ++it) {
                delete *it;
            };
            _writeErrorDetails.reset();
        }

        _wcErrDetails.reset();
    }

    void BatchedCommandResponse::cloneTo(BatchedCommandResponse* other) const {
        other->clear();

        other->_ok = _ok;
        other->_isOkSet = _isOkSet;

        other->_errCode = _errCode;
        other->_isErrCodeSet = _isErrCodeSet;

        other->_errMessage = _errMessage;
        other->_isErrMessageSet = _isErrMessageSet;

        other->_nModified = _nModified;
        other->_isNModifiedSet = _isNModifiedSet;

        other->_n = _n;
        other->_isNSet = _isNSet;

        other->_singleUpserted = _singleUpserted;
        other->_isSingleUpsertedSet = _isSingleUpsertedSet;

        other->unsetUpsertDetails();
        if (_upsertDetails.get()) {
            for (std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
                 it != _upsertDetails->end();
                 ++it) {
                BatchedUpsertDetail* upsertDetailsItem = new BatchedUpsertDetail;
                (*it)->cloneTo(upsertDetailsItem);
                other->addToUpsertDetails(upsertDetailsItem);
            }
        }

        other->_lastOp = _lastOp;
        other->_isLastOpSet = _isLastOpSet;

        other->_electionId = _electionId;
        other->_isElectionIdSet = _isElectionIdSet;

        other->unsetErrDetails();
        if (_writeErrorDetails.get()) {
            for(std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
                it != _writeErrorDetails->end();
                ++it) {
                WriteErrorDetail* errDetailsItem = new WriteErrorDetail;
                (*it)->cloneTo(errDetailsItem);
                other->addToErrDetails(errDetailsItem);
            }
        }

        if (_wcErrDetails.get()) {
            other->_wcErrDetails.reset(new WCErrorDetail());
            _wcErrDetails->cloneTo(other->_wcErrDetails.get());
        }
    }

    std::string BatchedCommandResponse::toString() const {
        return toBSON().toString();
    }

    void BatchedCommandResponse::setOk(int ok) {
        _ok = ok;
        _isOkSet = true;
    }

    void BatchedCommandResponse::unsetOk() {
         _isOkSet = false;
     }

    bool BatchedCommandResponse::isOkSet() const {
         return _isOkSet;
    }

    int BatchedCommandResponse::getOk() const {
        dassert(_isOkSet);
        return _ok;
    }

    void BatchedCommandResponse::setErrCode(int errCode) {
        _errCode = errCode;
        _isErrCodeSet = true;
    }

    void BatchedCommandResponse::unsetErrCode() {
         _isErrCodeSet = false;
     }

    bool BatchedCommandResponse::isErrCodeSet() const {
         return _isErrCodeSet;
    }

    int BatchedCommandResponse::getErrCode() const {
        if ( _isErrCodeSet ) {
            return _errCode;
        }
        else {
            return errCode.getDefault();
        }
    }

    void BatchedCommandResponse::setErrMessage(const StringData& errMessage) {
        _errMessage = errMessage.toString();
        _isErrMessageSet = true;
    }

    void BatchedCommandResponse::unsetErrMessage() {
         _isErrMessageSet = false;
     }

    bool BatchedCommandResponse::isErrMessageSet() const {
         return _isErrMessageSet;
    }

    const std::string& BatchedCommandResponse::getErrMessage() const {
        dassert(_isErrMessageSet);
        return _errMessage;
    }

    void BatchedCommandResponse::setNModified(long long n) {
        _nModified = n;
        _isNModifiedSet = true;
    }

    void BatchedCommandResponse::unsetNModified() {
         _isNModifiedSet = false;
     }

    bool BatchedCommandResponse::isNModified() const {
         return _isNModifiedSet;
    }

    long long BatchedCommandResponse::getNModified() const {
        if ( _isNModifiedSet ) {
            return _nModified;
        }
        else {
            return nModified.getDefault();
        }
    }

    void BatchedCommandResponse::setN(long long n) {
        _n = n;
        _isNSet = true;
    }

    void BatchedCommandResponse::unsetN() {
         _isNSet = false;
     }

    bool BatchedCommandResponse::isNSet() const {
         return _isNSet;
    }

    long long BatchedCommandResponse::getN() const {
        if ( _isNSet ) {
            return _n;
        }
        else {
            return n.getDefault();
        }
    }

    void BatchedCommandResponse::setUpsertDetails(
        const std::vector<BatchedUpsertDetail*>& upsertDetails) {
        unsetUpsertDetails();
        for (std::vector<BatchedUpsertDetail*>::const_iterator it = upsertDetails.begin();
             it != upsertDetails.end();
             ++it) {
            auto_ptr<BatchedUpsertDetail> tempBatchedUpsertDetail(new BatchedUpsertDetail);
            (*it)->cloneTo(tempBatchedUpsertDetail.get());
            addToUpsertDetails(tempBatchedUpsertDetail.release());
        }
    }

    void BatchedCommandResponse::addToUpsertDetails(BatchedUpsertDetail* upsertDetails) {
        if (_upsertDetails.get() == NULL) {
            _upsertDetails.reset(new std::vector<BatchedUpsertDetail*>);
        }
        _upsertDetails->push_back(upsertDetails);
    }

    void BatchedCommandResponse::unsetUpsertDetails() {
        if (_upsertDetails.get() != NULL) {
            for (std::vector<BatchedUpsertDetail*>::iterator it = _upsertDetails->begin();
                 it != _upsertDetails->end();
                 ++it) {
                delete *it;
            }
            _upsertDetails.reset();
        }
    }

    bool BatchedCommandResponse::isUpsertDetailsSet() const {
        return _upsertDetails.get() != NULL;
    }

    size_t BatchedCommandResponse::sizeUpsertDetails() const {
        dassert(_upsertDetails.get());
        return _upsertDetails->size();
    }

    const std::vector<BatchedUpsertDetail*>& BatchedCommandResponse::getUpsertDetails() const {
        dassert(_upsertDetails.get());
        return *_upsertDetails;
    }

    const BatchedUpsertDetail* BatchedCommandResponse::getUpsertDetailsAt(size_t pos) const {
        dassert(_upsertDetails.get());
        dassert(_upsertDetails->size() > pos);
        return _upsertDetails->at(pos);
    }

    void BatchedCommandResponse::setLastOp(OpTime lastOp) {
        _lastOp = lastOp;
        _isLastOpSet = true;
    }

    void BatchedCommandResponse::unsetLastOp() {
         _isLastOpSet = false;
     }

    bool BatchedCommandResponse::isLastOpSet() const {
         return _isLastOpSet;
    }

    OpTime BatchedCommandResponse::getLastOp() const {
        dassert(_isLastOpSet);
        return _lastOp;
    }

    void BatchedCommandResponse::setElectionId(const OID& electionId) {
        _electionId = electionId;
        _isElectionIdSet = true;
    }

    void BatchedCommandResponse::unsetElectionId() {
         _isElectionIdSet = false;
     }

    bool BatchedCommandResponse::isElectionIdSet() const {
         return _isElectionIdSet;
    }

    OID BatchedCommandResponse::getElectionId() const {
        dassert(_isElectionIdSet);
        return _electionId;
    }

    void BatchedCommandResponse::setErrDetails(const std::vector<WriteErrorDetail*>& errDetails) {
        unsetErrDetails();
        for (std::vector<WriteErrorDetail*>::const_iterator it = errDetails.begin();
             it != errDetails.end();
             ++it) {
            auto_ptr<WriteErrorDetail> tempBatchErrorDetail(new WriteErrorDetail);
            (*it)->cloneTo(tempBatchErrorDetail.get());
            addToErrDetails(tempBatchErrorDetail.release());
        }
    }

    void BatchedCommandResponse::addToErrDetails(WriteErrorDetail* errDetails) {
        if (_writeErrorDetails.get() == NULL) {
            _writeErrorDetails.reset(new std::vector<WriteErrorDetail*>);
        }
        _writeErrorDetails->push_back(errDetails);
    }

    void BatchedCommandResponse::unsetErrDetails() {
        if (_writeErrorDetails.get() != NULL) {
            for(std::vector<WriteErrorDetail*>::iterator it = _writeErrorDetails->begin();
                it != _writeErrorDetails->end();
                ++it) {
                delete *it;
            }
            _writeErrorDetails.reset();
        }
    }

    bool BatchedCommandResponse::isErrDetailsSet() const {
        return _writeErrorDetails.get() != NULL;
    }

    size_t BatchedCommandResponse::sizeErrDetails() const {
        dassert(_writeErrorDetails.get());
        return _writeErrorDetails->size();
    }

    const std::vector<WriteErrorDetail*>& BatchedCommandResponse::getErrDetails() const {
        dassert(_writeErrorDetails.get());
        return *_writeErrorDetails;
    }

    const WriteErrorDetail* BatchedCommandResponse::getErrDetailsAt(size_t pos) const {
        dassert(_writeErrorDetails.get());
        dassert(_writeErrorDetails->size() > pos);
        return _writeErrorDetails->at(pos);
    }

    void BatchedCommandResponse::setWriteConcernError(WCErrorDetail* error) {
        _wcErrDetails.reset(error);
    }

    void BatchedCommandResponse::unsetWriteConcernError() {
        _wcErrDetails.reset();
    }

    bool BatchedCommandResponse::isWriteConcernErrorSet() const {
        return _wcErrDetails.get();
    }

    const WCErrorDetail* BatchedCommandResponse::getWriteConcernError() const {
        return _wcErrDetails.get();
    }

} // namespace mongo
