/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2002 Chris Schoeneman, Nick Bolton, Sorin Sbarnea
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file COPYING that should have accompanied this file.
 * 
 * This package 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "CLog.h"
#include "CString.h"
#include "CStringUtil.h"
#include "LogOutputters.h"
#include "CArch.h"
#include "Version.h"
#include <cstdio>
#include <cstring>
#include <iostream>
#include <ctime> 

// names of priorities
static const char*    g_priority[] = {
                "FATAL",
                "ERROR",
                "WARNING",
                "NOTE",
                "INFO",
				"DEBUG",
				"DEBUG1",
				"DEBUG2",
				"DEBUG3",
				"DEBUG4",
				"DEBUG5"
              };

// number of priorities
static const int g_numPriority = (int)(sizeof(g_priority) / sizeof(g_priority[0]));

// the default priority
#if defined(NDEBUG)
static const int    g_defaultMaxPriority = 4;
#else
static const int    g_defaultMaxPriority = 5;
#endif

// length of longest string in g_priority
static const int    g_maxPriorityLength = 7;

// length of suffix string (": ")
static const int    g_prioritySuffixLength = 2;

// amount of padded required to fill in the priority prefix
static const int    g_priorityPad = g_maxPriorityLength +
                    g_prioritySuffixLength;


//
// CLog
//

CLog*         CLog::s_log = NULL;

CLog::CLog()
{
  assert(s_log == NULL);

  // create mutex for multithread safe operation
  m_mutex            = ARCH->newMutex();

  // other initalization
  m_maxPriority      = g_defaultMaxPriority;
  m_maxNewlineLength = 0;
  insert(new CConsoleLogOutputter);
}

CLog::~CLog()
{
  // clean up
  for (COutputterList::iterator index  = m_outputters.begin();
                  index != m_outputters.end(); ++index) {
    delete *index;
  }
  for (COutputterList::iterator index  = m_alwaysOutputters.begin();
                  index != m_alwaysOutputters.end(); ++index) {
    delete *index;
  }
  ARCH->closeMutex(m_mutex);
  s_log = NULL;
}

CLog*
CLog::getInstance()
{
  // note -- not thread safe;  client must initialize log safely
  if (s_log == NULL) {
    s_log = new CLog;
  }
  return s_log;
}

const char*
CLog::getFilterName() const
{
	return getFilterName(getFilter());
}

const char*
CLog::getFilterName(int level) const
{
	return g_priority[level];
}

void
CLog::print(const char* file, int line, const char* fmt, ...)
{
  // check if fmt begins with a priority argument
  ELevel priority = kINFO;
  if (fmt[0] == '%' && fmt[1] == 'z') {

	  // 060 in octal is 0 (48 in decimal), so subtracting this converts ascii
	  // number it a true number. we could use atoi instead, but this is how
	  // it was done originally.
	  priority = (ELevel)(fmt[2] - '\060'); // TODO: fix this shit

	  // move the pointer on past the debug priority char
	  fmt += 3;
  }

  // done if below priority threshold
  if (priority > getFilter()) {
    return;
  }

  // compute prefix padding length
  char stack[1024];

  // compute suffix padding length
  int sPad = m_maxNewlineLength;

  // print to buffer, leaving space for a newline at the end and prefix
  // at the beginning.
  char* buffer = stack;
  int len      = (int)(sizeof(stack) / sizeof(stack[0]));
  while (true) {
    // try printing into the buffer
    va_list args;
    va_start(args, fmt);
    int n = ARCH->vsnprintf(buffer, len  - sPad, fmt, args);
    va_end(args);

    // if the buffer wasn't big enough then make it bigger and try again
    if (n < 0 || n > (int)len) {
      if (buffer != stack) {
        delete[] buffer;
      }
      len   *= 2;
      buffer = new char[len];
    }

    // if the buffer was big enough then continue
    else {
      break;
    }
  }

  // print the prefix to the buffer.  leave space for priority label.
  // do not prefix time and file for kPRINT (CLOG_PRINT)
  if ((file != NULL) && (priority != kPRINT)) {

      char message[2048];
	  struct tm *tm;
	  char tmp[220];
	  time_t t;
	  time(&t);
	  tm = localtime(&t);
	  sprintf(tmp, "%04i-%02i-%02iT%02i:%02i:%02i", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
	  //strcpy(msg, tmp);

	  
	  sprintf(message, "%s %s: %s\n\t%s,%d", tmp, g_priority[priority], buffer, file, line);
//    buffer[pPad - 1] = ' ';

    // discard file and line if priority < 0
    /*if (priority < 0) {
      message += pPad - g_priorityPad;
    }
	*/
	  // output buffer
	  output(priority, message);
  } else {
	  output(priority, buffer);
  }

  // clean up
  if (buffer != stack) {
    delete[] buffer;
  }
}

void
CLog::insert(ILogOutputter* outputter, bool alwaysAtHead)
{
  assert(outputter != NULL);

  CArchMutexLock lock(m_mutex);
  if (alwaysAtHead) {
    m_alwaysOutputters.push_front(outputter);
  }
  else {
    m_outputters.push_front(outputter);
  }

  outputter->open(kAppVersion);

  // Issue 41
  // don't show log unless user requests it, as some users find this
  // feature irritating (i.e. when they lose network connectivity).
  // in windows the log window can be displayed by selecting "show log"
  // from the synergy system tray icon.
  // if this causes problems for other architectures, then a different
  // work around should be attempted.
  //outputter->show(false);
}

void
CLog::remove(ILogOutputter* outputter)
{
  CArchMutexLock lock(m_mutex);
  m_outputters.remove(outputter);
  m_alwaysOutputters.remove(outputter);
}

void
CLog::pop_front(bool alwaysAtHead)
{
  CArchMutexLock lock(m_mutex);
  COutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters;
  if (!list->empty()) {
    delete list->front();
    list->pop_front();
  }
}

bool
CLog::setFilter(const char* maxPriority)
{
  if (maxPriority != NULL) {
    for (int i = 0; i < g_numPriority; ++i) {
      if (strcmp(maxPriority, g_priority[i]) == 0) {
        setFilter(i);
        return true;
      }
    }
    return false;
  }
  return true;
}

void
CLog::setFilter(int maxPriority)
{
  CArchMutexLock lock(m_mutex);
  m_maxPriority = maxPriority;
}

int
CLog::getFilter() const
{
  CArchMutexLock lock(m_mutex);
  return m_maxPriority;
}

void
CLog::output(ELevel priority, char* msg)
{
  assert(priority >= -1 && priority < g_numPriority);
  assert(msg != NULL);
  if (!msg) return;

  CArchMutexLock lock(m_mutex);

  COutputterList::const_iterator i;

  for (i = m_alwaysOutputters.begin(); i != m_alwaysOutputters.end(); ++i) {

	  // write to outputter
	  (*i)->write(priority, msg);
  }

  for (i = m_outputters.begin(); i != m_outputters.end(); ++i) {

	  // write to outputter and break out of loop if it returns false
	  if (!(*i)->write(priority, msg)) {
		  break;
	  }
  }
}