//********************************************************************
//
// THIS MODULE IS PUBLICLY LICENSED
//
// Copyright 2001-2012 by Wilson Snyder.  This program is free software;
// you can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License Version 2.0.
//
// This 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 General Public License
// for more details.
//
//********************************************************************
///
/// \file
/// \brief SystemPerl: File logging, redirection of cout,cerr
///
/// AUTHOR:  Wilson Snyder
///
//********************************************************************

#include <cstdarg>

#include "sp_log.h"

//**********************************************************************
// Static

list<sp_log_file*> sp_log_file::s_fileps;	///< List of open sp_log_files

//**********************************************************************
// Opening

void sp_log_file::open (const char *filename, open_mode_t append) {
    // Open main logfile
    m_filename = filename;
    m_splitNum = 0;
    open_int(filename, append);
}

void sp_log_file::open_int (string filename, open_mode_t append) {
    // Internal open, used also for split
    this->close();
    std::ofstream::open (filename.c_str(), append);
    if (std::ofstream::is_open()) {
	m_isOpen = true;
	add_file();
    }
}

//**********************************************************************
// Closing

void sp_log_file::close() {
    end_redirect();
    close_int();
}

void sp_log_file::close_int() {
    if (m_isOpen) {
        if (!this->flush()) {
            std::cerr <<"%Error: sp_log_file:close_int(): flusing writes of '" <<m_filename<<"'"<<endl;
        }
	m_isOpen = false;
	remove_file();
	std::ofstream::close();
    }
}

//**********************************************************************
// Split

void sp_log_file::split_now () {
    close_int();

    // We rename the first file, so it will be obvious we rolled.
    // This also has the nice effect of insuring downstream tools notice all revs.
    if (m_splitNum==0) {
	string newname = split_name(0);
	rename (m_filename.c_str(), newname.c_str());
	// We'll just ignore if there's an error with the rename
    }
    m_splitNum++;

    open_int(split_name(m_splitNum));
}

string sp_log_file::split_name (unsigned suffixNum) {
    string filename = m_filename;
    char rollnum[10];
    sprintf(rollnum, "_%03d", suffixNum);
    unsigned pos = filename.rfind(".log");
    if (pos == filename.length()-4) {
	// Foo.log -> Foo_###.log
	filename.erase(pos);
	filename = filename + rollnum + ".log";
    } else {
	// Foo -> Foo_###
	filename += rollnum;
    }
    return filename;
}

void sp_log_file::split_check () {
    if (isOpen() && m_splitSize && (tellp() > m_splitSize)) {
	split_now();
    }
}

//**********************************************************************
// Redirection

void sp_log_file::redirect_cout() {
    if (m_strmOldCout) {
	end_redirect();
    }
    m_tee = new sp_log_teebuf (std::cout.rdbuf(), rdbuf());

    // Save old
    m_strmOldCout = std::cout.rdbuf();
    m_strmOldCerr = std::cerr.rdbuf();
    // Redirect
    std::cout.rdbuf (m_tee);
    std::cerr.rdbuf (m_tee);
}

void sp_log_file::end_redirect() {
    if (m_strmOldCout) {
	std::cout.rdbuf (m_strmOldCout);
	std::cerr.rdbuf (m_strmOldCerr);
	m_strmOldCout = NULL;
	m_strmOldCerr = NULL;
	delete (m_tee);
    }
}

//**********************************************************************
// Flushing

void sp_log_file::add_file() {
    s_fileps.push_back(this);
}
void sp_log_file::remove_file() {
    s_fileps.remove(this);
}
void sp_log_file::flush_all() {
    // Flush every open file, or on more recent library, those we know about
    // And they call this progress? :(
    for (list<sp_log_file*>::iterator it = s_fileps.begin(); it != s_fileps.end(); ++it) {
	(*it)->flush();
    }
#if defined(__GNUC__) && __GNUC__ >= 3
#else
    std::streambuf::flush_all();
#endif
}

//**********************************************************************
// C compatibility

extern "C" void sp_log_printf(const char *format, ...) {
#if defined(__GNUC__) && __GNUC__ >= 3
    // And they call this progress? :(
    static const size_t BUFSIZE = 64*1024;
    char buffer[BUFSIZE];
    va_list ap;
    va_start (ap, format);
    vsnprintf(buffer, BUFSIZE, format, ap);
    buffer[BUFSIZE-1] = '\0';
    std::cout<<buffer;
#else
    va_list ap;
    va_start (ap, format);
    std::cout.vform (format, ap);
#endif
}

//**********************************************************************

#ifdef SP_LOG_MAIN
//make -k stream && ./stream && echo "----" && cat sim.log
int main () {
    sp_log_file simlog ("sim.log");
//    sp_log_file simlog;
//    simlog.open ("sim.log");
    simlog << "Hello simlog!\n";

    simlog.redirect_cout ();
    sp_log_printf ("%s", "Hello C\n");
    cout << "Hello C++\n";
}
#endif

//g++ -DSP_LOG_MAIN sp_log.cpp ; ./a.out && cat sim.log