/*
 *      Copyright (C) 2005-2008 Team XBMC
 *      http://www.xbmc.org
 *
 *  This Program 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 2, 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 XBMC; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "system.h"
#include "log.h"
#include "stdio_utf8.h"
#include "stat_utf8.h"
#include "utils/StdString.h"

static FILE*       m_file           = NULL;
static int         m_repeatCount    = 0;
static int         m_repeatLogLevel = -1;
static std::string m_repeatLine     = "";
static int         m_logLevel       = LOG_LEVEL_NONE;

static pthread_mutex_t   m_log_mutex;

static char levelNames[][8] =
{"DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "SEVERE", "FATAL", "NONE"};

CLog::CLog()
{}

CLog::~CLog()
{}

void CLog::Close()
{
  if (m_file)
  {
    fclose(m_file);
    m_file = NULL;
  }
  m_repeatLine.clear();
  pthread_mutex_destroy(&m_log_mutex);
}

void CLog::Log(int loglevel, const char *format, ... )
{
  pthread_mutex_lock(&m_log_mutex);

  static const char* prefixFormat = "%02.2d:%02.2d:%02.2d T:%" PRIu64 " %7s: ";
#if !(defined(_DEBUG) || defined(PROFILE))
  if (m_logLevel > LOG_LEVEL_NORMAL ||
     (m_logLevel > LOG_LEVEL_NONE && loglevel >= LOGNOTICE))
#endif
  {
    if (!m_file)
    {
      pthread_mutex_unlock(&m_log_mutex);
      return;
    }

    struct timeval now;
    gettimeofday(&now, NULL);
    SYSTEMTIME time;
    time.wHour=(now.tv_sec/3600) % 24;
    time.wMinute=(now.tv_sec/60) % 60;
    time.wSecond=now.tv_sec % 60;
    uint64_t stamp = now.tv_usec + now.tv_sec * 1000000;
    CStdString strPrefix, strData;

    strData.reserve(16384);
    va_list va;
    va_start(va, format);
    strData.FormatV(format,va);
    va_end(va);

    if (m_repeatLogLevel == loglevel && m_repeatLine == strData)
    {
      m_repeatCount++;
      pthread_mutex_unlock(&m_log_mutex);
      return;
    }
    else if (m_repeatCount)
    {
      CStdString strData2;
      strPrefix.Format(prefixFormat, time.wHour, time.wMinute, time.wSecond, stamp, levelNames[m_repeatLogLevel]);

      strData2.Format("Previous line repeats %d times." LINE_ENDING, m_repeatCount);
      fputs(strPrefix.c_str(), m_file);
      fputs(strData2.c_str(), m_file);
      OutputDebugString(strData2);
      m_repeatCount = 0;
    }
    
    m_repeatLine      = strData;
    m_repeatLogLevel  = loglevel;

    unsigned int length = 0;
    while ( length != strData.length() )
    {
      length = strData.length();
      strData.TrimRight(" ");
      strData.TrimRight('\n');
      strData.TrimRight("\r");
    }

    if (!length)
    {
      pthread_mutex_unlock(&m_log_mutex);
      return;
    }
    
    OutputDebugString(strData);

    /* fixup newline alignment, number of spaces should equal prefix length */
    strData.Replace("\n", LINE_ENDING"                                            ");
    strData += LINE_ENDING;

    strPrefix.Format(prefixFormat, time.wHour, time.wMinute, time.wSecond, stamp, levelNames[loglevel]);

    fputs(strPrefix.c_str(), m_file);
    fputs(strData.c_str(), m_file);
    //fputs(strPrefix.c_str(), stdout);
    //fputs(strData.c_str(), stdout);
    fflush(m_file);
  }

  pthread_mutex_unlock(&m_log_mutex);
}

bool CLog::Init(const char* path)
{
  pthread_mutex_init(&m_log_mutex, NULL);
  if (m_logLevel > LOG_LEVEL_NONE) { 
  if (!m_file)
  {
    CStdString strLogFile, strLogFileOld;

    strLogFile.Format("%s/omxplayer.log", path);
    strLogFileOld.Format("%s/omxplayer.old.log", path);

    struct stat info;
    if (stat(strLogFileOld.c_str(),&info) == 0 &&
        remove(strLogFileOld.c_str()) != 0)
      return false;
    if (stat(strLogFile.c_str(),&info) == 0 &&
        rename(strLogFile.c_str(),strLogFileOld.c_str()) != 0)
      return false;

    m_file = fopen(strLogFile.c_str(),"wb");
  }

  if (m_file)
  {
    unsigned char BOM[3] = {0xEF, 0xBB, 0xBF};
    fwrite(BOM, sizeof(BOM), 1, m_file);
  }
  }
  return m_file != NULL;
}

void CLog::MemDump(char *pData, int length)
{
  if (m_logLevel > LOG_LEVEL_NONE) { 
  Log(LOGDEBUG, "MEM_DUMP: Dumping from %p", pData);
  for (int i = 0; i < length; i+=16)
  {
    CStdString strLine;
    strLine.Format("MEM_DUMP: %04x ", i);
    char *alpha = pData;
    for (int k=0; k < 4 && i + 4*k < length; k++)
    {
      for (int j=0; j < 4 && i + 4*k + j < length; j++)
      {
        CStdString strFormat;
        strFormat.Format(" %02x", *pData++);
        strLine += strFormat;
      }
      strLine += " ";
    }
    // pad with spaces
    while (strLine.size() < 13*4 + 16)
      strLine += " ";
    for (int j=0; j < 16 && i + j < length; j++)
    {
      if (*alpha > 31)
        strLine += *alpha;
      else
        strLine += '.';
      alpha++;
    }
    Log(LOGDEBUG, "%s", strLine.c_str());
  }
  }
}

void CLog::SetLogLevel(int level)
{
  if(m_logLevel > LOG_LEVEL_NONE)
    CLog::Log(LOGNOTICE, "Log level changed to %d", m_logLevel);
  m_logLevel = level;
}

int CLog::GetLogLevel()
{
  return m_logLevel;
}

void CLog::OutputDebugString(const std::string& line)
{
#if defined(_DEBUG) || defined(PROFILE)
if(m_logLevel > LOG_LEVEL_NONE) {
  ::OutputDebugString(line.c_str());
  ::OutputDebugString("\n");
}
#endif
}
