// @file namespacestring.h

/**
*    Copyright (C) 2008 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 <algorithm>
#include <string>

#include "mongo/base/string_data.h"
#include "mongo/util/assert_util.h"

namespace mongo {

    /* in the mongo source code, "client" means "database". */

    const size_t MaxDatabaseNameLen = 128; // max str len for the db name, including null char

    /** @return true if a client can modify this namespace even though it is under ".system."
        For example <dbname>.system.users is ok for regular clients to update.
        @param write used when .system.js
    */
    bool legalClientSystemNS( const StringData& ns , bool write );

    /* e.g.
       NamespaceString ns("acme.orders");
       cout << ns.coll; // "orders"
    */
    class NamespaceString {
    public:
        /**
         * Constructs an empty NamespaceString.
         */
        NamespaceString();

        /**
         * Constructs a NamespaceString from the fully qualified namespace named in "ns".
         */
        explicit NamespaceString( const StringData& ns );

        /**
         * Constructs a NamespaceString for the given database and collection names.
         * "dbName" must not contain a ".", and "collectionName" must not start with one.
         */
        NamespaceString( const StringData& dbName, const StringData& collectionName );

        StringData db() const;
        StringData coll() const;

        const std::string& ns() const { return _ns; }

        operator const std::string&() const { return ns(); }
        const std::string& toString() const { return ns(); }

        size_t size() const { return _ns.size(); }

        bool isSystem() const { return coll().startsWith( "system." ); }
        bool isSystemDotIndexes() const { return coll() == "system.indexes"; }
        bool isConfigDB() const { return db() == "config"; }
        bool isCommand() const { return coll() == "$cmd"; }
        bool isOplog() const { return oplog( _ns ); }
        bool isSpecialCommand() const { return coll().startsWith("$cmd.sys"); }
        bool isSpecial() const { return special( _ns ); }
        bool isNormal() const { return normal( _ns ); }

        /**
         * @return true if the namespace is valid. Special namespaces for internal use are considered as valid.
         */
        bool isValid() const { return validDBName( db() ) && !coll().empty(); }

        bool operator==( const std::string& nsIn ) const { return nsIn == _ns; }
        bool operator==( const NamespaceString& nsIn ) const { return nsIn._ns == _ns; }

        bool operator!=( const std::string& nsIn ) const { return nsIn != _ns; }
        bool operator!=( const NamespaceString& nsIn ) const { return nsIn._ns != _ns; }

        bool operator<( const NamespaceString& rhs ) const { return _ns < rhs._ns; }

        /** ( foo.bar ).getSisterNS( "blah" ) == foo.blah
         */
        std::string getSisterNS( const StringData& local ) const;

        // @return db() + ".system.indexes"
        std::string getSystemIndexesCollection() const;

        // @return db() + ".$cmd"
        std::string getCommandNS() const;

        /**
         * @return true if ns is 'normal'.  A "$" is used for namespaces holding index data,
         * which do not contain BSON objects in their records. ("oplog.$main" is the exception)
         */
        static bool normal(const StringData& ns);

        /**
         * @return true if the ns is an oplog one, otherwise false.
         */
        static bool oplog(const StringData& ns);

        static bool special(const StringData& ns);

        /**
         * samples:
         *   good
         *      foo
         *      bar
         *      foo-bar
         *   bad:
         *      foo bar
         *      foo.bar
         *      foo"bar
         *
         * @param db - a possible database name
         * @return if db is an allowed database name
         */
        static bool validDBName( const StringData& dbin );

        /**
         * Takes a fully qualified namespace (ie dbname.collectionName), and returns true if
         * the collection name component of the namespace is valid.
         * samples:
         *   good:
         *      foo.bar
         *   bad:
         *      foo.
         *
         * @param ns - a full namespace (a.b)
         * @return if db.coll is an allowed collection name
         */
        static bool validCollectionComponent(const StringData& ns);

        /**
         * Takes a collection name and returns true if it is a valid collection name.
         * samples:
         *   good:
         *     foo
         *     system.indexes
         *   bad:
         *     $foo
         * @param coll - a collection name component of a namespace
         * @return if the input is a valid collection name
         */
        static bool validCollectionName(const StringData& coll);

    private:

        std::string _ns;
        size_t _dotIndex;
    };


    // "database.a.b.c" -> "database"
    inline StringData nsToDatabaseSubstring( const StringData& ns ) {
        size_t i = ns.find( '.' );
        if ( i == std::string::npos ) {
            massert(10078, "nsToDatabase: db too long", ns.size() < MaxDatabaseNameLen );
            return ns;
        }
        massert(10088, "nsToDatabase: db too long", i < static_cast<size_t>(MaxDatabaseNameLen));
        return ns.substr( 0, i );
    }

    // "database.a.b.c" -> "database"
    inline void nsToDatabase(const StringData& ns, char *database) {
        StringData db = nsToDatabaseSubstring( ns );
        db.copyTo( database, true );
    }

    // TODO: make this return a StringData
    inline std::string nsToDatabase(const StringData& ns) {
        return nsToDatabaseSubstring( ns ).toString();
    }

    // "database.a.b.c" -> "a.b.c"
    inline StringData nsToCollectionSubstring( const StringData& ns ) {
        size_t i = ns.find( '.' );
        massert(16886, "nsToCollectionSubstring: no .", i != std::string::npos );
        return ns.substr( i + 1 );
    }


    /**
     * NamespaceDBHash and NamespaceDBEquals allow you to do something like
     * unordered_map<string,int,NamespaceDBHash,NamespaceDBEquals>
     * and use the full namespace for the string
     * but comparisons are done only on the db piece
     */

    /**
     * this can change, do not store on disk
     */
    int nsDBHash( const std::string& ns );

    bool nsDBEquals( const std::string& a, const std::string& b );

    struct NamespaceDBHash {
        int operator()( const std::string& ns ) const {
            return nsDBHash( ns );
        }
    };

    struct NamespaceDBEquals {
        bool operator()( const std::string& a, const std::string& b ) const {
            return nsDBEquals( a, b );
        }
    };

}


#include "mongo/db/namespace_string-inl.h"
