/* ======================================================================== *\
!
! *
! * This file is part of MARS, the MAGIC Analysis and Reconstruction
! * Software. It is distributed to you in the hope that it can be a useful
! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
! * It is distributed WITHOUT ANY WARRANTY.
! *
! * Permission to use, copy, modify and distribute this software and its
! * documentation for any purpose is hereby granted without fee,
! * provided that the above copyright notice appear in all copies and
! * that both that copyright notice and this permission notice appear
! * in supporting documentation. It is provided "as is" without express
! * or implied warranty.
! *
!
!
!   Author(s): Thomas Bretz  12/2000 <mailto:tbretz@uni-sw.gwdg.de>
!
!   Copyright: MAGIC Software Development, 2000-2001
!
!
\* ======================================================================== */


//////////////////////////////////////////////////////////////////////////////
//                                                                          //
// MLog                                                                     //
//                                                                          //
// This is what we call the logging-system.                                 //
//                                                                          //
// It is derived from the C++ streaming classes and can handle our          //
// logging. The log output can be redirected to stdout, stderr, any other   //
// stream or a root window.                                                 //
//                                                                          //
// There is a global log-instance which you can use like cout, id is gLog.  //
// A log-instance of your choice (gLog by default) is destributed to all    //
// Task which are used in an eventloop, so that you can redirect the output //
// of one eventloop to where you want..                                     //
//                                                                          //
// The MLog stream has the advantage, that it can be used like the common   //
// C++ streams (for example cout). It can redirect the stream to different  //
// outputs (console, file, GUI) if necessary at the same time.              //
//                                                                          //
// It supports different debug levels. The debug level of the current       //
// stream contents is set by SetDebugLevel, the output level of the         //
// current stream can be set by SetOutputLevel.                             //
//                                                                          //
// The header file MLogManip.h contains so called manipulators (like flush  //
// or setw from iomanip.h) which can manipulate these levels from within    //
// stream, for example:                                                     //
//    gLog << debug(3) << "Hallo World " << endl;                           //
// sets the debug level of the following stream to 3                        //
//                                                                          //
// edev(), ddev() can be used to enable/disable an output device from       //
// within the stream. The enumerations are defined in MLog::_flags          //
//                                                                          //
// Commonly used abbreviations are also defined:                            //
//    dbginf  Prints source file name and line number. Used for output      //
//            which people may like to look up in the code                  //
//    all     Is streamed to the output in any case. Used for outputs       //
//            which are requested by the user (eg TObject::Print)           //
//    err     Should be used for fatal errors which stops the current       //
//            processing, eg:                                               //
//              gLog << err << "ERROR: TObject::Copy - Stopped" << endl;    //
//    warn    Warning means an error occured, but it is not clear whether   //
//            this results further procesing or not.                        //
//    inf     Informs the user about what's going on. Mostly usefull for    //
//            debugging, but in general not necessary at all.               //
//                                                                          //
// gLog is a global stream defined like cout or cerr                        //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#include "MLog.h"

#include <stdlib.h>     // mkstempe
#include <fstream.h>
#ifdef _REENTRANT
#include <pthread.h>
#endif
#include <TGListBox.h>

#include "MLogManip.h"

ClassImp(MLog);

// root 3.02:
// check for TObjectWarning, TObject::Info, gErrorIgnoreLevel

//
// This is the definition of the global log facility
//
MLog gLog;

// --------------------------------------------------------------------------
//
// this strange usage of an unbufferd buffer is a workaround
// to make it work on Alpha and Linux!
//
void MLog::Init()
{
    setp(&fBuffer, &fBuffer+1);
    *this << '\0';

#ifdef _REENTRANT
    //
    // Creat drawing semaphore
    //
    fMuxGui = new pthread_mutex_t;
    pthread_mutex_init((pthread_mutex_t*)fMuxGui, NULL);
#endif
}

// --------------------------------------------------------------------------
//
// default constructor which initializes the streamer and sets the device
// which is used for the output (i)
//
MLog::MLog(int i) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(i), fIsNull(kFALSE), fGuiLineId(0), fout(NULL), fOutAllocated(kFALSE), fgui(NULL), fNumLines(0)
{
    Init();
}

// --------------------------------------------------------------------------
//
// default constructor which initializes the streamer and sets the given
// ofstream as the default output device
//
MLog::MLog(ofstream &out) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eFile), fIsNull(kFALSE), fGuiLineId(0),  fout(&out), fOutAllocated(kFALSE), fgui(NULL), fNumLines(0)
{
    Init();
}

// --------------------------------------------------------------------------
//
// default constructor which initializes the streamer and sets the given
// TGListBox as the default output device
//
MLog::MLog(TGListBox &out) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eGui), fGuiLineId(0),  fout(NULL), fOutAllocated(kFALSE), fgui(&out), fNumLines(0)
{
    Init();
}

// --------------------------------------------------------------------------
//
// default constructor which initializes the streamer and opens a file with
// the given name. Dependend on the flag the file is set as output device
// or not.
//
MLog::MLog(const char *fname, int flag) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eFile), fIsNull(kFALSE), fGuiLineId(0),  fgui(NULL), fNumLines(0)
{
    Init();

    AllocateFile(fname);
    CheckFlag(eFile, flag);
}

// --------------------------------------------------------------------------
//
//  Destructor, destroying the gui mutex.
//
MLog::~MLog()
{
    DeallocateFile();
#ifdef _REENTRANT
    pthread_mutex_destroy((pthread_mutex_t*)fMuxGui);
#endif
}

// --------------------------------------------------------------------------
//
// copyt constructor
//
MLog::MLog(MLog &log)
{
    fOutputLevel  = log.fOutputLevel;
    fDebugLevel   = log.fDebugLevel;
    fDevice       = log.fDevice;
}

// --------------------------------------------------------------------------
//
// This is the function which writes the stream physically to a device.
// If you want to add a new device this must be done here.
//
void MLog::WriteBuffer()
{
    //
    // restart writing to the buffer at its first char
    //
    const int len = fPPtr - fBase;

    fPPtr = fBase;

    if (fIsNull)
        return;

    if (fDevice&eStdout)
        cout.write(fBase, len);

    if (fDevice&eStderr)
        cerr.write(fBase, len);

    if (fDevice&eFile && fout)
        fout->write(fBase, len);

    if (fDevice&eGui && fgui)
    {
        char **newstr = new char*[fNumLines+1];

        for (int i=0; i<fNumLines; i++)
            newstr[i] = fGuiLines[i];

        if (fNumLines>0)
            delete fGuiLines;

        char *dummy = new char[len];
        memcpy(dummy, fBase, len-1);
        dummy[len-1]='\0';

        newstr[fNumLines++] = dummy;

        fGuiLines = newstr;
    }
}

void MLog::UpdateGui()
{
    if (fNumLines==0)
        return;

    Lock();

    for (int i=0; i<fNumLines; i++)
    {
        fgui->AddEntry(fGuiLines[i], fGuiLineId++);
        delete fGuiLines[i];
    }

    delete fGuiLines;

    fNumLines=0;

    fgui->RemoveEntries(0, fGuiLineId-1000);
    fgui->SetTopEntry(fGuiLineId-1);
    fgui->SetBit(kHasChanged);

    UnLock();
}

void MLog::Lock()
{
#ifdef _REENTRANT
    pthread_mutex_lock((pthread_mutex_t*)fMuxGui);
#endif
}

void MLog::UnLock()
{
#ifdef _REENTRANT
    pthread_mutex_unlock((pthread_mutex_t*)fMuxGui);
#endif
}

// --------------------------------------------------------------------------
//
// This is called to flush the buffer of the streaming devices
//
int MLog::sync()
{
    Lock();
    WriteBuffer();
    UnLock();

    if (fDevice&eStdout)
        cout.flush();

    if (fDevice&eStderr)
        cerr.flush();

    if (fDevice&eFile && fout)
        fout->flush();

    return 0;
}

// --------------------------------------------------------------------------
//
// This function comes from streambuf and should
// output the buffer to the device (flush, endl)
// or handle a buffer overflow (too many chars)
// If a real overflow happens i contains the next
// chars which doesn't fit into the buffer anymore.
// If the buffer is not really filled i is EOF(-1).
//
int MLog::overflow(int i) // i=EOF means not a real overflow
{
    //
    // no output if
    //
    if (fOutputLevel <= fDebugLevel)
    {
        Lock();

        *fPPtr++ = (char)i;

        if (fPPtr == fEPtr)
            WriteBuffer();

        UnLock();
    }

    return 0;
}

// --------------------------------------------------------------------------
//
// Create a new instance of an file output stream
// an set the corresponding flag
//
void MLog::AllocateFile(const char *fname)
{
    char *txt = (char*)"logXXXXXX";
    fout = fname ? new ofstream(fname) : new ofstream(mkstemp(txt));
    fOutAllocated = kTRUE;
}

// --------------------------------------------------------------------------
//
// if fout was allocated by this instance of MLooging
// delete it.
//
void MLog::DeallocateFile()
{
    if (fOutAllocated)
        delete fout;
}

// --------------------------------------------------------------------------
//
// if necessary delete the old in stance of the file
// output stream and create a new one
//
void MLog::ReallocateFile(const char *fname)
{
    DeallocateFile();
    AllocateFile(fname);
}

// --------------------------------------------------------------------------
//
// This function checks if a device should get enabled or disabled.
//
void MLog::CheckFlag(Flags_t chk, int flag)
{
    if (flag==-1)
        return;

    flag ? EnableOutputDevice(chk) : DisableOutputDevice(chk);
}
