// @file log.h

/*    Copyright 2009 10gen Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#pragma once

#include <string.h>
#include <errno.h>
#include "../bson/util/builder.h"

#ifndef _WIN32
//#include <syslog.h>
#endif

namespace mongo {

    enum LogLevel {  LL_DEBUG , LL_INFO , LL_NOTICE , LL_WARNING , LL_ERROR , LL_SEVERE };

    inline const char * logLevelToString( LogLevel l ) {
        switch ( l ) {
        case LL_DEBUG:
        case LL_INFO:
        case LL_NOTICE:
            return "";
        case LL_WARNING:
            return "warning" ;
        case LL_ERROR:
            return "ERROR";
        case LL_SEVERE:
            return "SEVERE";
        default:
            return "UNKNOWN";
        }
    }

    class LabeledLevel {
    public:

	LabeledLevel( int level ) : _level( level ) {}
	LabeledLevel( const char* label, int level ) : _label( label ), _level( level ) {}
	LabeledLevel( const string& label, int level ) : _label( label ), _level( level ) {}

	LabeledLevel operator+( int i ) const {
	    return LabeledLevel( _label, _level + i );
	}

	LabeledLevel operator+( const char* label ) const {
	    if( _label == "" )
		return LabeledLevel( label, _level );
	    return LabeledLevel( _label + string("::") + label, _level );
	}

	LabeledLevel operator+( string& label ) const {
	    return LabeledLevel( _label + string("::") + label, _level );
	}

	LabeledLevel operator-( int i ) const {
	    return LabeledLevel( _label, _level - i );
	}

	const string& getLabel() const { return _label; }
	int getLevel() const { return _level; }

    private:
	string _label;
	int _level;
    };

    class LazyString {
    public:
        virtual ~LazyString() {}
        virtual string val() const = 0;
    };

    // Utility class for stringifying object only when val() called.
    template< class T >
    class LazyStringImpl : public LazyString {
    public:
        LazyStringImpl( const T &t ) : t_( t ) {}
        virtual string val() const { return t_.toString(); }
    private:
        const T& t_;
    };

    class Tee {
    public:
        virtual ~Tee() {}
        virtual void write(LogLevel level , const string& str) = 0;
    };

    class Nullstream {
    public:
        virtual Nullstream& operator<< (Tee* tee) {
            return *this;
        }
        virtual ~Nullstream() {}
        virtual Nullstream& operator<<(const char *) {
            return *this;
        }
        virtual Nullstream& operator<<(const string& ) {
            return *this;
        }
        virtual Nullstream& operator<<(const StringData& ) {
            return *this;
        }
        virtual Nullstream& operator<<(char *) {
            return *this;
        }
        virtual Nullstream& operator<<(char) {
            return *this;
        }
        virtual Nullstream& operator<<(int) {
            return *this;
        }
        virtual Nullstream& operator<<(ExitCode) {
            return *this;
        }
        virtual Nullstream& operator<<(unsigned long) {
            return *this;
        }
        virtual Nullstream& operator<<(long) {
            return *this;
        }
        virtual Nullstream& operator<<(unsigned) {
            return *this;
        }
        virtual Nullstream& operator<<(unsigned short) {
            return *this;
        }
        virtual Nullstream& operator<<(double) {
            return *this;
        }
        virtual Nullstream& operator<<(void *) {
            return *this;
        }
        virtual Nullstream& operator<<(const void *) {
            return *this;
        }
        virtual Nullstream& operator<<(long long) {
            return *this;
        }
        virtual Nullstream& operator<<(unsigned long long) {
            return *this;
        }
        virtual Nullstream& operator<<(bool) {
            return *this;
        }
        virtual Nullstream& operator<<(const LazyString&) {
            return *this;
        }
        template< class T >
        Nullstream& operator<<(T *t) {
            return operator<<( static_cast<void*>( t ) );
        }
        template< class T >
        Nullstream& operator<<(const T *t) {
            return operator<<( static_cast<const void*>( t ) );
        }
        template< class T >
        Nullstream& operator<<(const shared_ptr<T> p ) {
            T * t = p.get();
            if ( ! t )
                *this << "null";
            else
                *this << *t;
            return *this;
        }
        template< class T >
        Nullstream& operator<<(const T &t) {
            return operator<<( static_cast<const LazyString&>( LazyStringImpl< T >( t ) ) );
        }

        virtual Nullstream& operator<< (ostream& ( *endl )(ostream&)) {
            return *this;
        }
        virtual Nullstream& operator<< (ios_base& (*hex)(ios_base&)) {
            return *this;
        }

        virtual void flush(Tee *t = 0) {}
    };
    extern Nullstream nullstream;

    class Logstream : public Nullstream {
        static mongo::mutex mutex;
        static int doneSetup;
        stringstream ss;
        int indent;
        LogLevel logLevel;
        static FILE* logfile;
        static boost::scoped_ptr<ostream> stream;
        static vector<Tee*> * globalTees;
    public:
        inline static void logLockless( const StringData& s );

        static void setLogFile(FILE* f) {
            scoped_lock lk(mutex);
            logfile = f;
        }

        static int magicNumber() {
            return 1717;
        }

        static int getLogDesc() {
            int fd = -1;
            if (logfile != NULL)
#if defined(_WIN32)
                // the ISO C++ conformant name is _fileno
                fd = _fileno( logfile );
#else
                fd = fileno( logfile );
#endif
            return fd;
        }

        inline void flush(Tee *t = 0);

        inline Nullstream& setLogLevel(LogLevel l) {
            logLevel = l;
            return *this;
        }

        /** note these are virtual */
        Logstream& operator<<(const char *x) { ss << x; return *this; }
        Logstream& operator<<(const string& x) { ss << x; return *this; }
        Logstream& operator<<(const StringData& x) { ss << x.data(); return *this; }
        Logstream& operator<<(char *x)       { ss << x; return *this; }
        Logstream& operator<<(char x)        { ss << x; return *this; }
        Logstream& operator<<(int x)         { ss << x; return *this; }
        Logstream& operator<<(ExitCode x)    { ss << x; return *this; }
        Logstream& operator<<(long x)          { ss << x; return *this; }
        Logstream& operator<<(unsigned long x) { ss << x; return *this; }
        Logstream& operator<<(unsigned x)      { ss << x; return *this; }
        Logstream& operator<<(unsigned short x){ ss << x; return *this; }
        Logstream& operator<<(double x)        { ss << x; return *this; }
        Logstream& operator<<(void *x)         { ss << x; return *this; }
        Logstream& operator<<(const void *x)   { ss << x; return *this; }
        Logstream& operator<<(long long x)     { ss << x; return *this; }
        Logstream& operator<<(unsigned long long x) { ss << x; return *this; }
        Logstream& operator<<(bool x)               { ss << x; return *this; }

        Logstream& operator<<(const LazyString& x) {
            ss << x.val();
            return *this;
        }
        Nullstream& operator<< (Tee* tee) {
            ss << '\n';
            flush(tee);
            return *this;
        }
        Logstream& operator<< (ostream& ( *_endl )(ostream&)) {
            ss << '\n';
            flush(0);
            return *this;
        }
        Logstream& operator<< (ios_base& (*_hex)(ios_base&)) {
            ss << _hex;
            return *this;
        }

        Logstream& prolog() {
            return *this;
        }

        void addGlobalTee( Tee * t ) {
            if ( ! globalTees )
                globalTees = new vector<Tee*>();
            globalTees->push_back( t );
        }
        
        void indentInc(){ indent++; }
        void indentDec(){ indent--; }
        int getIndent() const { return indent; }

    private:
        static thread_specific_ptr<Logstream> tsp;
        Logstream() {
            indent = 0;
            _init();
        }
        void _init() {
            ss.str("");
            logLevel = LL_INFO;
        }
    public:
        static Logstream& get() {
            if ( StaticObserver::_destroyingStatics ) {
                cout << "Logstream::get called in uninitialized state" << endl;
            }
            Logstream *p = tsp.get();
            if( p == 0 )
                tsp.reset( p = new Logstream() );
            return *p;
        }
    };

    extern int logLevel;
    extern int tlogLevel;

    inline Nullstream& out( int level = 0 ) {
        if ( level > logLevel )
            return nullstream;
        return Logstream::get();
    }

    /* flush the log stream if the log level is
       at the specified level or higher. */
    inline void logflush(int level = 0) {
        if( level > logLevel )
            Logstream::get().flush(0);
    }

    /* without prolog */
    inline Nullstream& _log( int level = 0 ) {
        if ( level > logLevel )
            return nullstream;
        return Logstream::get();
    }

    /** logging which we may not want during unit tests (dbtests) runs.
        set tlogLevel to -1 to suppress tlog() output in a test program. */
    inline Nullstream& tlog( int level = 0 ) {
        if ( level > tlogLevel || level > logLevel )
            return nullstream;
        return Logstream::get().prolog();
    }

    inline Nullstream& log( int level ) {
        if ( level > logLevel )
            return nullstream;
        return Logstream::get().prolog();
    }

#define MONGO_LOG(level) if ( MONGO_unlikely(logLevel >= (level)) ) log( level )
#define LOG MONGO_LOG

    inline Nullstream& log( LogLevel l ) {
        return Logstream::get().prolog().setLogLevel( l );
    }

    inline Nullstream& log( const LabeledLevel& ll ) {
        Nullstream& stream = log( ll.getLevel() );
        if( ll.getLabel() != "" )
            stream << "[" << ll.getLabel() << "] ";
        return stream;
    }

    inline Nullstream& log() {
        return Logstream::get().prolog();
    }

    inline Nullstream& error() {
        return log( LL_ERROR );
    }

    inline Nullstream& warning() {
        return log( LL_WARNING );
    }

    /* default impl returns "" -- mongod overrides */
    extern const char * (*getcurns)();

    inline Nullstream& problem( int level = 0 ) {
        if ( level > logLevel )
            return nullstream;
        Logstream& l = Logstream::get().prolog();
        l << ' ' << getcurns() << ' ';
        return l;
    }

    /**
       log to a file rather than stdout
       defined in assert_util.cpp
     */
    void initLogging( const string& logpath , bool append );
    void rotateLogs( int signal = 0 );

    std::string toUtf8String(const std::wstring& wide);

    inline string errnoWithDescription(int x = errno) {
        stringstream s;
        s << "errno:" << x << ' ';

#if defined(_WIN32)
        LPTSTR errorText = NULL;
        FormatMessage(
            FORMAT_MESSAGE_FROM_SYSTEM
            |FORMAT_MESSAGE_ALLOCATE_BUFFER
            |FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            x, 0,
            (LPTSTR) &errorText,  // output
            0, // minimum size for output buffer
            NULL);
        if( errorText ) {
            string x = toUtf8String(errorText);
            for( string::iterator i = x.begin(); i != x.end(); i++ ) {
                if( *i == '\n' || *i == '\r' )
                    break;
                s << *i;
            }
            LocalFree(errorText);
        }
        else
            s << strerror(x);
        /*
        DWORD n = FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, x,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf, 0, NULL);
        */
#else
        s << strerror(x);
#endif
        return s.str();
    }

    /** output the error # and error message with prefix.
        handy for use as parm in uassert/massert.
        */
    string errnoWithPrefix( const char * prefix );

    void Logstream::logLockless( const StringData& s ) {
        if ( s.size() == 0 )
            return;

        if ( doneSetup == 1717 ) {
            if (fwrite(s.data(), s.size(), 1, logfile)) {
                fflush(logfile);
            }
            else {
                int x = errno;
                cout << "Failed to write to logfile: " << errnoWithDescription(x) << endl;
            }
        }
        else {
            cout << s.data();
            cout.flush();
        }
    }

    void Logstream::flush(Tee *t) {
        // this ensures things are sane
        if ( doneSetup == 1717 ) {
            string msg = ss.str();
            string threadName = getThreadName();
            const char * type = logLevelToString(logLevel);

            int spaceNeeded = (int)(msg.size() + 64 + threadName.size());
            int bufSize = 128;
            while ( bufSize < spaceNeeded )
                bufSize += 128;

            BufBuilder b(bufSize);
            time_t_to_String( time(0) , b.grow(20) );
            if (!threadName.empty()) {
                b.appendChar( '[' );
                b.appendStr( threadName , false );
                b.appendChar( ']' );
                b.appendChar( ' ' );
            }

            for ( int i=0; i<indent; i++ )
                b.appendChar( '\t' );

            if ( type[0] ) {
                b.appendStr( type , false );
                b.appendStr( ": " , false );
            }

            b.appendStr( msg );

            string out( b.buf() , b.len() - 1);

            scoped_lock lk(mutex);

            if( t ) t->write(logLevel,out);
            if ( globalTees ) {
                for ( unsigned i=0; i<globalTees->size(); i++ )
                    (*globalTees)[i]->write(logLevel,out);
            }

#ifndef _WIN32
            //syslog( LOG_INFO , "%s" , cc );
#endif
            if(fwrite(out.data(), out.size(), 1, logfile)) {
                fflush(logfile);
            }
            else {
                int x = errno;
                cout << "Failed to write to logfile: " << errnoWithDescription(x) << ": " << out << endl;
            }
        }
        _init();
    }

    struct LogIndentLevel {
        LogIndentLevel(){
            Logstream::get().indentInc();
        }
        ~LogIndentLevel(){
            Logstream::get().indentDec();
        }
    };

    extern Tee* const warnings; // Things put here go in serverStatus

} // namespace mongo
