/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mia/core/threadedmsg.hh>
#include <mia/core/parallel.hh>
#include <mia/core/msgstream.hh>

#include <iostream>
#include <iomanip>
#include <sstream>
#include <stdexcept>

NS_MIA_BEGIN
using std::setw;
using std::setfill;
using std::logic_error;


class thread_streamredir: public std::streambuf
{
public:
       thread_streamredir();

       static void set_master_stream(std::ostream& master);
protected:
       int sync();
       int overflow(int c);
       std::streamsize xsputn ( const char *s, std::streamsize n );
private:
       void send_to_master();

       std::ostringstream m_buffer;
       int m_id;
       static std::ostream *m_master;
       static CMutex m_master_lock;
       static int m_next_id;
};


thread_streamredir::thread_streamredir()
{
       CScopedLock lock(m_master_lock);
       m_id = m_next_id++;
}


void thread_streamredir::set_master_stream(std::ostream& master)
{
       m_master = &master;
}

int thread_streamredir::sync()
{
       if (!m_buffer.str().empty())
              send_to_master();

       return 0;
}

int thread_streamredir::overflow(int c)
{
       // this function probably never gets called, because we use a stringstream
       // to buffer the output that can grow until the memory is filled
       send_to_master();
       m_buffer << (char)c;
       return 0;
}

std::streamsize thread_streamredir::xsputn( const char *s, std::streamsize n )
{
       int i = 0;

       while (i < n) {
              // newline forces syncronization and output to the master stream
              if (*s != '\n') {
                     m_buffer << *s;

                     // since the ostringstream grows
                     // dynamically, this should never happen.
                     // Should the size be limited?
                     if (m_buffer.fail())
                            break;
              } else
                     send_to_master();

              ++s;
              ++i;
       }

       return i;
}

void thread_streamredir::send_to_master()
{
       CScopedLock lock(m_master_lock);
       char oldfill = m_master->fill();
       *m_master << "[thread " << setw(3) << setfill('0') << m_id << setfill(oldfill) << "]:"
                 << m_buffer.str() << "\n";
       m_buffer.str("");
       m_buffer.clear();
}

std::ostream *thread_streamredir::m_master = &std::cerr;
CMutex thread_streamredir::m_master_lock;
int thread_streamredir::m_next_id = 0;


CThreadMsgStream::CThreadMsgStream():
       // coverity[resource_leak] explanation below in destructor
       std::ostream(new thread_streamredir()),
       m_old(vstream::instance().set_stream(*this))
{
}

CThreadMsgStream::~CThreadMsgStream()
{
       flush();
       vstream::instance().set_stream(m_old);
       // At this point we delete the thread_streamredir object that is
       // created in the constructor
       delete rdbuf();
}

void CThreadMsgStream::do_set_master_stream(std::ostream& master)
{
       // if this is actually a CThreadMsgStream, then deadlock is certain
       // hence throw and tell the programmer to fix the program
       if (dynamic_cast<CThreadMsgStream *>(&master)) {
              throw logic_error("CThreadMsgStream::set_master_stream: trying to set the master stream to a CThreadMsgStream. "
                                "Since this would make deadlock certain I bail out now.\nPlease fix your program\n");
       }

       thread_streamredir::set_master_stream(master);
}

NS_MIA_END
