/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "Engine/StdH.h"

#include <Engine/Base/Console.h>
#include <Engine/Base/Console_internal.h>

#include <Engine/Base/Timer.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/CTString.h>
#include <Engine/Base/FileName.h>
#include <Engine/Base/Memory.h>

#include <Engine/Math/Functions.h>

CConsole *_pConsole = NULL;

extern INDEX con_iLastLines;
BOOL con_bCapture = FALSE;
CTString con_strCapture = "";


// Constructor.
CConsole::CConsole(void)
{
  con_strBuffer  = NULL;
  con_strLineBuffer = NULL;
  con_atmLines = NULL;
  con_fLog = NULL;
}
// Destructor.
CConsole::~CConsole(void)
{
  ASSERT(this!=NULL);
  if (con_fLog!=NULL) {
    fclose(con_fLog);
    con_fLog = NULL;
  }
  if (con_strBuffer!=NULL) {
    FreeMemory(con_strBuffer);
  }
  if (con_strLineBuffer!=NULL) {
    FreeMemory(con_strLineBuffer);
  }
  if (con_atmLines!=NULL) {
    FreeMemory(con_atmLines);
  }
}

// Initialize the console.
void CConsole::Initialize(const CTFileName &fnmLog, INDEX ctCharsPerLine, INDEX ctLines)
{
  con_csConsole.cs_iIndex = -1;
  // synchronize access to console
  CTSingleLock slConsole(&con_csConsole, TRUE);

  // allocate the buffer
  con_ctCharsPerLine = ctCharsPerLine;
  con_ctLines        = ctLines;
  con_ctLinesPrinted = 0;
  // note: we add +1 for '\n' perline and +1 '\0' at the end of buffer
  con_strBuffer = (char *)AllocMemory((ctCharsPerLine+1)*ctLines+1);
  con_strLineBuffer = (char *)AllocMemory(ctCharsPerLine+2); // includes '\n' and '\0'
  con_atmLines = (TIME*)AllocMemory((ctLines+1)*sizeof(TIME));
  // make it empty
  for(INDEX iLine=0; iLine<ctLines; iLine++) {
    ClearLine(iLine);
  }
  // add string terminator at the end
  con_strBuffer[(ctCharsPerLine+1)*ctLines] = 0;

  // start printing in last line
  con_strLastLine = con_strBuffer+(ctCharsPerLine+1)*(ctLines-1);
  con_strCurrent = con_strLastLine;

  // open console file
  con_fLog = fopen(fnmLog, "wt");

  if (con_fLog==NULL) {
    FatalError("%s", strerror(errno));
  }

  // print one dummy line on start
  CPrintF("\n");
}

// Get current console buffer.
const char *CConsole::GetBuffer(void)
{
  ASSERT(this!=NULL);
  return con_strBuffer+(con_ctLines-con_ctLinesPrinted)*(con_ctCharsPerLine+1);
}
INDEX CConsole::GetBufferSize(void)
{
  ASSERT(this!=NULL);
  return (con_ctCharsPerLine+1)*con_ctLines+1;
}

// Discard timing info for last lines
void CConsole::DiscardLastLineTimes(void)
{
  ASSERT(this!=NULL);
  for(INDEX i=0; i<con_ctLines; i++) {
    con_atmLines[i] = -10000.0f;
  }
}

// Get number of lines newer than given time
INDEX CConsole::NumberOfLinesAfter(TIME tmLast)
{
  ASSERT(this!=NULL);
  // clamp console variable
  con_iLastLines = Clamp( con_iLastLines, 0, (INDEX)CONSOLE_MAXLASTLINES);
  // find number of last console lines to be displayed on screen
  for(INDEX i=0; i<con_iLastLines; i++) {
    if (con_atmLines[con_ctLines-1-i]<tmLast) {
      return i;
    }
  }
  return con_iLastLines;
}

// Get one of last lines
CTString CConsole::GetLastLine(INDEX iLine)
{
  ASSERT(this!=NULL);
  if (iLine>=con_ctLinesPrinted) {
    return "";
  }
  ASSERT(iLine>=0 && iLine<con_ctLines);
  // get line number from the start of buffer
  iLine = con_ctLines-1-iLine;
  // copy line
  memcpy(con_strLineBuffer, con_strBuffer+iLine*(con_ctCharsPerLine+1), con_ctCharsPerLine);
  // put terminator at the end
  con_strLineBuffer[con_ctCharsPerLine] = 0;
  // return it
  return con_strLineBuffer;
}

// clear one given line in buffer
void CConsole::ClearLine(INDEX iLine)
{
  ASSERT(this!=NULL);
  // line must be valid
  ASSERT(iLine>=0 && iLine<con_ctLines);
  // get start of line
  char *pchLine = con_strBuffer+iLine*(con_ctCharsPerLine+1);
  // fill it with spaces
  memset(pchLine, ' ', con_ctCharsPerLine);
  // add return at the end of line
  pchLine[con_ctCharsPerLine] = '\n';
  con_atmLines[iLine] = _pTimer!=NULL?_pTimer->GetRealTimeTick():0.0f;
}

// scroll buffer up, discarding lines at the start
void CConsole::ScrollBufferUp(INDEX ctLines)
{
  ASSERT(this!=NULL);
  ASSERT(ctLines>0 && ctLines<con_ctLines);
  // move buffer up
  memmove(
    con_strBuffer, 
    con_strBuffer+ctLines*(con_ctCharsPerLine+1),
    (con_ctLines-ctLines)*(con_ctCharsPerLine+1));
  // move buffer up
  memmove(
    con_atmLines, 
    con_atmLines+ctLines,
    (con_ctLines-ctLines)*sizeof(TIME));
  con_ctLinesPrinted = ClampUp(con_ctLinesPrinted+1, con_ctLines);
  // clear lines at the end
  for(INDEX iLine=con_ctLines-ctLines; iLine<con_ctLines; iLine++) {
    ClearLine(iLine);
  }
}

// Add a line of text to console
void CConsole::PutString(const char *strString)
{
  ASSERT(this!=NULL);
  // synchronize access to console
  CTSingleLock slConsole(&con_csConsole, TRUE);

  // if in debug version, report it to output window
  _RPT1(_CRT_WARN, "%s", strString);
  // first append that string to the console output file
  if (con_fLog!=NULL) {
    fprintf(con_fLog, "%s", strString);
    fflush(con_fLog);
  }
  // if needed, append to capture string
  if (con_bCapture) {
    con_strCapture+=strString;
  }

  // if dedicated server
  extern BOOL _bDedicatedServer;
  if (_bDedicatedServer) {
    // print to output
    printf("%s", strString);
  }

  // start at the beginning of the string
  const char *pch=strString;
  // while not end of string
  while(*pch!=0) {
    // if line buffer full
    if (con_strCurrent==con_strLastLine+con_ctCharsPerLine) {
      // move buffer up
      ScrollBufferUp(1);
      // restart new line
      con_strCurrent=con_strLastLine;
    }
    // get char
    char c = *pch++;
    // skip cr
    if (c=='\r') {
      continue;
    }
    // if it is end of line
    if (c=='\n') {
      // move buffer up
      ScrollBufferUp(1);
      // restart new line
      con_strCurrent=con_strLastLine;
      continue;
    }
    // otherwise, add the char to buffer
    *con_strCurrent++ = c;
  }
}

// Close console log file buffers (call only when force-exiting!)
void CConsole::CloseLog(void)
{
  ASSERT(this!=NULL);
  if (con_fLog!=NULL) {
    fclose(con_fLog);
  }
  con_fLog = NULL;
}

// Print formated text to the main console.
void CPrintF(const char *strFormat, ...)
{
  if (_pConsole==NULL) {
    return;
  }
  // format the message in buffer
  va_list arg;
  va_start(arg, strFormat);
  CTString strBuffer;
  strBuffer.VPrintF(strFormat, arg);
  va_end(arg);

  // print it to the main console
  _pConsole->PutString(strBuffer);
}

// Add a string of text to console
void CPutString(const char *strString)
{
  if (_pConsole==NULL) {
    return;
  }
  _pConsole->PutString(strString);
}

// Get number of lines newer than given time
INDEX CON_NumberOfLinesAfter(TIME tmLast)
{
  if (_pConsole==NULL) {
    return 0;
  }
  return _pConsole->NumberOfLinesAfter(tmLast);
}
// Get one of last lines
CTString CON_GetLastLine(INDEX iLine)
{
  if (_pConsole==NULL) {
    return "";
  }
  return _pConsole->GetLastLine(iLine);
}
// Discard timing info for last lines
void CON_DiscardLastLineTimes(void)
{
  if (_pConsole==NULL) {
    return;
  }
  _pConsole->DiscardLastLineTimes();
}
// Get current console buffer.
const char *CON_GetBuffer(void)
{
  if (_pConsole==NULL) {
    return "";
  }
  return _pConsole->GetBuffer();
}
INDEX CON_GetBufferSize(void)
{
  if (_pConsole==NULL) {
    return 1;
  }
  return _pConsole->GetBufferSize();
}
