/**
*    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 "mongo/db/repl/oplogreader.h"

/* replication data overview

   at the slave:
     local.sources { host: ..., source: ..., only: ..., syncedTo: ..., localLogTs: ..., dbsNextPass: { ... }, incompleteCloneDbs: { ... } }

   at the master:
     local.oplog.$<source>
*/

namespace mongo {

    class Database;
    class TransactionExperiment;

    // Main entry point for master/slave at startup time.
    void startMasterSlave();

    // externed for use with resync.cpp
    extern volatile int relinquishSyncingSome;
    extern volatile int syncing;

    // Global variable that contains a string telling why master/slave halted
    extern const char *replAllDead;

    /* A replication exception */
    class SyncException : public DBException {
    public:
        SyncException() : DBException( "sync exception" , 10001 ) {}
    };

    namespace threadpool {
        class ThreadPool;
    }

    /* A Source is a source from which we can pull (replicate) data.
       stored in collection local.sources.

       Can be a group of things to replicate for several databases.

          { host: ..., source: ..., only: ..., syncedTo: ..., dbsNextPass: { ... }, incompleteCloneDbs: { ... } }

       'source' defaults to 'main'; support for multiple source names is
       not done (always use main for now).
    */
    class ReplSource {
        shared_ptr<threadpool::ThreadPool> tp;

        void resync(TransactionExperiment* txn, const std::string& dbName);

        /** @param alreadyLocked caller already put us in write lock if true */
        void sync_pullOpLog_applyOperation(BSONObj& op, bool alreadyLocked);

        /* pull some operations from the master's oplog, and apply them.
           calls sync_pullOpLog_applyOperation
        */
        int sync_pullOpLog(int& nApplied);

        /* we only clone one database per pass, even if a lot need done.  This helps us
           avoid overflowing the master's transaction log by doing too much work before going
           back to read more transactions. (Imagine a scenario of slave startup where we try to
           clone 100 databases in one pass.)
        */
        set<string> addDbNextPass;

        set<string> incompleteCloneDbs;

        BSONObj _me;

        ReplSource();

        void resyncDrop( TransactionExperiment* txn, const string& db );
        // call without the db mutex
        void syncToTailOfRemoteLog();
        string ns() const { return string( "local.oplog.$" ) + sourceName(); }
        unsigned _sleepAdviceTime;

        /**
         * If 'db' is a new database and its name would conflict with that of
         * an existing database, synchronize these database names with the
         * master.
         * @return true iff an op with the specified ns may be applied.
         */
        bool handleDuplicateDbName( TransactionExperiment* txn,
                                    const BSONObj &op,
                                    const char* ns,
                                    const char* db );

        // populates _me so that it can be passed to oplogreader for handshakes
        void ensureMe();

    public:
        OplogReader oplogReader;

        void applyOperation(TransactionExperiment* txn, Database* db, const BSONObj& op);
        string hostName;    // ip addr or hostname plus optionally, ":<port>"
        string _sourceName;  // a logical source name.
        string sourceName() const { return _sourceName.empty() ? "main" : _sourceName; }
        string only; // only a certain db. note that in the sources collection, this may not be changed once you start replicating.

        /* the last time point we have already synced up to (in the remote/master's oplog). */
        OpTime syncedTo;

        int nClonedThisPass;

        typedef vector< shared_ptr< ReplSource > > SourceVector;
        static void loadAll(SourceVector&);
        explicit ReplSource(BSONObj);

        /* -1 = error */
        int sync(int& nApplied);

        void save(); // write ourself to local.sources

        // make a jsobj from our member fields of the form
        //   { host: ..., source: ..., syncedTo: ... }
        BSONObj jsobj();

        bool operator==(const ReplSource&r) const {
            return hostName == r.hostName && sourceName() == r.sourceName();
        }
        string toString() const { return sourceName() + "@" + hostName; }

        bool haveMoreDbsToSync() const { return !addDbNextPass.empty(); }
        int sleepAdvice() const {
            if ( !_sleepAdviceTime )
                return 0;
            int wait = _sleepAdviceTime - unsigned( time( 0 ) );
            return wait > 0 ? wait : 0;
        }

        static bool throttledForceResyncDead( TransactionExperiment* txn, const char *requester );
        static void forceResyncDead( TransactionExperiment* txn, const char *requester );
        void forceResync( TransactionExperiment* txn, const char *requester );
    };

    /**
     * Helper class used to set and query an ignore state for a named database.
     * The ignore state will expire after a specified OpTime.
     */
    class DatabaseIgnorer {
    public:
        /** Indicate that operations for 'db' should be ignored until after 'futureOplogTime' */
        void doIgnoreUntilAfter( const string &db, const OpTime &futureOplogTime );
        /**
         * Query ignore state of 'db'; if 'currentOplogTime' is after the ignore
         * limit, the ignore state will be cleared.
         */
        bool ignoreAt( const string &db, const OpTime &currentOplogTime );
    private:
        map< string, OpTime > _ignores;
    };

}
