// bsonobjiterator.h

/*    Copyright 2009 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.
 */

#pragma once

#include <boost/preprocessor/cat.hpp> // like the ## operator but works with __LINE__

#include "mongo/bson/bsonobj.h"
#include "mongo/base/disallow_copying.h"

namespace mongo {

    /** iterator for a BSONObj

       Note each BSONObj ends with an EOO element: so you will get more() on an empty
       object, although next().eoo() will be true.

       The BSONObj must stay in scope for the duration of the iterator's execution.

       todo: we may want to make a more stl-like iterator interface for this
             with things like begin() and end()
    */
    class BSONObjIterator {
    public:
        /** Create an iterator for a BSON object.
        */
        BSONObjIterator(const BSONObj& jso) {
            int sz = jso.objsize();
            if ( MONGO_unlikely(sz == 0) ) {
                _pos = _theend = 0;
                return;
            }
            _pos = jso.objdata() + 4;
            _theend = jso.objdata() + sz - 1;
        }

        BSONObjIterator( const char * start , const char * end ) {
            _pos = start + 4;
            _theend = end - 1;
        }

        /** @return true if more elements exist to be enumerated. */
        bool more() { return _pos < _theend; }

        /** @return true if more elements exist to be enumerated INCLUDING the EOO element which is always at the end. */
        bool moreWithEOO() { return _pos <= _theend; }

        /** @return the next element in the object. For the final element, element.eoo() will be true. */
        BSONElement next( bool checkEnd ) {
            verify( _pos <= _theend );
            
            int maxLen = -1;
            if ( checkEnd ) {
                maxLen = _theend + 1 - _pos;
                verify( maxLen > 0 );
            }

            BSONElement e( _pos, maxLen );
            int esize = e.size( maxLen );
            massert( 16446, "BSONElement has bad size", esize > 0 );
            _pos += esize;

            return e;
        }
        BSONElement next() {
            verify( _pos <= _theend );
            BSONElement e(_pos);
            _pos += e.size();
            return e;
        }
        void operator++() { next(); }
        void operator++(int) { next(); }

        BSONElement operator*() {
            verify( _pos <= _theend );
            return BSONElement(_pos);
        }

    private:
        const char* _pos;
        const char* _theend;
    };

    /** Base class implementing ordered iteration through BSONElements. */
    class BSONIteratorSorted {
        MONGO_DISALLOW_COPYING(BSONIteratorSorted);
    public:
        ~BSONIteratorSorted() {
            verify( _fields );
            delete[] _fields;
            _fields = 0;
        }

        bool more() {
            return _cur < _nfields;
        }

        BSONElement next() {
            verify( _fields );
            if ( _cur < _nfields )
                return BSONElement( _fields[_cur++] );
            return BSONElement();
        }

    protected:
        class ElementFieldCmp;
        BSONIteratorSorted( const BSONObj &o, const ElementFieldCmp &cmp );
        
    private:
        const char ** _fields;
        int _nfields;
        int _cur;
    };

    /** Provides iteration of a BSONObj's BSONElements in lexical field order. */
    class BSONObjIteratorSorted : public BSONIteratorSorted {
    public:
        BSONObjIteratorSorted( const BSONObj &object );
    };

    /**
     * Provides iteration of a BSONArray's BSONElements in numeric field order.
     * The elements of a bson array should always be numerically ordered by field name, but this
     * implementation re-sorts them anyway.
     */
    class BSONArrayIteratorSorted : public BSONIteratorSorted {
    public:
        BSONArrayIteratorSorted( const BSONArray &array );
    };

    /** Similar to BOOST_FOREACH
     *
     *  because the iterator is defined outside of the for, you must use {} around
     *  the surrounding scope. Don't do this:
     *
     *  if (foo)
     *      BSONForEach(e, obj)
     *          doSomething(e);
     *
     *  but this is OK:
     *
     *  if (foo) {
     *      BSONForEach(e, obj)
     *          doSomething(e);
     *  }
     *
     */

#define BSONForEach(e, obj)                                     \
    BSONObjIterator BOOST_PP_CAT(it_,__LINE__)(obj);            \
    for ( BSONElement e;                                        \
            (BOOST_PP_CAT(it_,__LINE__).more() ?                  \
             (e = BOOST_PP_CAT(it_,__LINE__).next(), true) :  \
             false) ;                                         \
            /*nothing*/ )

}
