/*
 *  Copyright (c) 2007-2008 Cyrille Berger <cberger@cberger.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * either version 2, or (at your option) any later version of the License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "Debug.h"

#include <map>
#include <fstream>

#include "Macros_p.h"
#include "Token_p.h"
#include "Type.h"

using namespace Cauchy;

struct FunctionDebugInfo {
  FunctionDebugInfo() : enabled( true ) {}
  bool enabled;
};

struct FileDebugInfo {
  FileDebugInfo() : enabled( true ) {}
  bool enabled;
  std::map<Cauchy::String, FunctionDebugInfo> functionsDebugInfo;
};

struct LibraryDebugInfo {
  LibraryDebugInfo() : enabled( true ) {}
  bool enabled;
  std::map<Cauchy::String, FileDebugInfo> filesDebugInfo;
};

struct Debug::Private {
  Private();
  ~Private();
  // Static
  static Debug::Private* instance();
  static Debug::Private* s_instance;
  // Helper members
  static bool isEnabled( const std::map< Cauchy::String, LibraryDebugInfo >& informationRecords, const Cauchy::String& _libraryName, const Cauchy::String& _fileName, const Cauchy::String& _functionName );
  static String extractFunctionName( const String& );
  static void readConfigFile( const String& _fileName, std::map< Cauchy::String, LibraryDebugInfo >& destination);
  static std::ostream& report( std::ostream&, const std::map< Cauchy::String, LibraryDebugInfo >& _informationRecords, const String& _streamName, const String& _libraryName, const String& _fileName, int _line, const String& _functionName );
  // Fields
  std::ostream* m_debugStream;
  std::ostream* m_warningStream;
  std::ostream* m_errorStream;
  std::ostream* m_voidStream;
  std::map< Cauchy::String, LibraryDebugInfo > m_librariesDebugInfo;
  std::map< Cauchy::String, LibraryDebugInfo > m_librariesWarningInfo;
  std::map< Cauchy::String, LibraryDebugInfo > m_librariesErrorInfo;
};

Debug::Private* Debug::Private::s_instance = 0;

STATIC_DELETER( Debug )
{
  delete Debug::Private::s_instance;
}

class NullStream : public std::ostream {
  public:
    NullStream() : std::ostream(0) {}
};

Debug::Private::Private()
{
  m_debugStream = &std::cerr;
  m_warningStream = &std::cerr;
  m_errorStream = &std::cerr;
  m_voidStream = new NullStream;
//   m_librariesDebugInfo[ "Cauchy" ].enabled = false;
  // Read ${HOME}/.CauchyDebugConfig
  readConfigFile( ".CauchyDebugConfig", m_librariesDebugInfo );
  readConfigFile( ".CauchyWarningConfig", m_librariesWarningInfo );
  readConfigFile( ".CauchyErrorConfig", m_librariesErrorInfo );
}

Debug::Private::~Private()
{
  delete m_voidStream;
}

void Debug::Private::readConfigFile( const String& _fileName, std::map< Cauchy::String, LibraryDebugInfo >& _destination)
{
  String path = getenv("HOME");
  path += "/";
  path += _fileName;
  std::ifstream in;
  in.open(path.c_str() );
  if(in)
  {
    std::string cstr;
    std::getline(in,cstr);
    Cauchy::String str(cstr);
    while ( in ) {
      if( not str.isEmpty() and str[0] != '#' )
      {
        std::vector< String > splited = str.split( " =,", false );
        if( splited.size() >= 2 and splited.size() <= 4 )
        {
          std::vector< String >::iterator it = --splited.end();
          bool status = (*(--splited.end()) == "true");
          it = splited.begin();
          LibraryDebugInfo& ldi = _destination[ *it ];
          if( splited.size() == 2 )
          {
            ldi.enabled = status;
          } else {
            FileDebugInfo& fdi = ldi.filesDebugInfo[ *(++it) ];
            if( splited.size() == 3 )
            {
              fdi.enabled = status;
            } else {
              FunctionDebugInfo& fundi = fdi.functionsDebugInfo[ *(++it) ];
              fundi.enabled = status;
            }
          }
        }
      }
      std::getline(in,cstr);
      str = cstr;
    }
  }
}


bool Debug::Private::isEnabled( const std::map< Cauchy::String, LibraryDebugInfo >& informationRecords, const Cauchy::String& _libraryName, const Cauchy::String& _fileName, const Cauchy::String& _functionName )
{
  std::map< Cauchy::String, LibraryDebugInfo >::const_iterator ldi = informationRecords.find(_libraryName );
  if( ldi == informationRecords.end() ) return true;
  if( not ldi->second.enabled ) return false;
  std::map< Cauchy::String, FileDebugInfo>::const_iterator fdi = ldi->second.filesDebugInfo.find( _fileName );
  if( fdi == ldi->second.filesDebugInfo.end() ) return true;
  if( not fdi->second.enabled ) return false;
  std::map< Cauchy::String, FunctionDebugInfo>::const_iterator fundi = fdi->second.functionsDebugInfo.find( _functionName );
  if( fundi == fdi->second.functionsDebugInfo.end() ) return true;
  return fundi->second.enabled;
}

String Debug::Private::extractFunctionName( const String& _str)
{
  int posBeg = 0;
  int posEnd = 0;
  for( size_t i = 0; i < _str.length(); ++i)
  {
    if( _str[i] == '(' )
    {
      posEnd = i;
      break;
    }
  }
  for( size_t i = posEnd; i > 0; --i)
  {
    if( _str[i] == ' ' )
    {
      posBeg = i + 1;
      break;
    }
  }
  return _str.substr( posBeg, posEnd - posBeg );
}

std::ostream& Debug::Private::report( std::ostream& _stream, const std::map< Cauchy::String, LibraryDebugInfo >& _informationRecords, const String& _streamName, const String& _libraryName, const String& _fileName, int _line, const String& _functionName )
{
  Cauchy::String fileName = _fileName;
  Cauchy::String functionName = Private::extractFunctionName( _functionName );
  if( isEnabled( _informationRecords, _libraryName, fileName, functionName ) )
  {
    _stream << _libraryName << " (" << _streamName << "): in " << fileName << " at " << _line << " in " << functionName << ": ";
    return _stream;
  } else {
    return *Private::instance()->m_voidStream;
  }
}

bool Debug::isDebugEnabled( const String& _libraryName, const String& _fileName, const String& _functionName )
{
  Private* p = Private::instance();
  return p->isEnabled( p->m_librariesDebugInfo, _libraryName, _fileName, _functionName );
}

std::ostream& Debug::debug( const Cauchy::String& _libraryName, const Cauchy::String& _fileName, int _line, const Cauchy::String& _functionName )
{
  Private* p = Private::instance();
  return Private::report( *p->m_debugStream, p->m_librariesDebugInfo, "Debug", _libraryName, _fileName, _line, _functionName );
}

std::ostream& Debug::warning( const Cauchy::String& _libraryName, const Cauchy::String& _fileName, int _line, const Cauchy::String& _functionName )
{
  Private* p = Private::instance();
  return Private::report( *p->m_warningStream, p->m_librariesWarningInfo, "Warning", _libraryName, _fileName, _line, _functionName );
}

std::ostream& Debug::error( const Cauchy::String& _libraryName, const Cauchy::String& _fileName, int _line, const Cauchy::String& _functionName )
{
  Private* p = Private::instance();
  return Private::report( *p->m_errorStream, p->m_librariesErrorInfo, "Error", _libraryName, _fileName, _line, _functionName );
}


Debug::Private* Debug::Private::instance()
{
  if( s_instance == 0) s_instance = new Debug::Private;
  return s_instance;
}

namespace Cauchy {
  std::ostream& operator<< (std::ostream& ostr, const Cauchy::Token& token)
  {
    ostr << Token::typeToString( token.type );
    if( token.isPrimary() )
    {
      ostr << " primary";
    }
    if( token.type == Token::IDENTIFIER)
    {
      ostr << " " << token.string;
    }
    ostr << " at (" << token.line << ", " << token.column << ")";
    return ostr;
  }
  std::ostream& operator<< (std::ostream& ostr, const Type& type)
  {
    switch(type.dataType())
    {
      case Type::LOGICAL:
        ostr <<"LOGICAL";
        break;
      case Type::SINGLE:
        ostr <<"SINGLE";
        break;
      case Type::DOUBLE:
        ostr <<"DOUBLE";
        break;
      case Type::INTEGER:
        ostr <<"INTEGER";
        break;
      case Type::INT8:
        ostr <<"INT8";
        break;
      case Type::UINT8:
        ostr <<"UINT8";
        break;
      case Type::INT16:
        ostr <<"INT16";
        break;
      case Type::UINT16:
        ostr <<"UINT16";
        break;
      case Type::INT32:
        ostr <<"INT32";
        break;
      case Type::UINT32:
        ostr <<"UINT32";
        break;
      case Type::INT64:
        ostr <<"INT64";
        break;
      case Type::UINT64:
        ostr <<"UINT64";
        break;
      case Type::COMPLEX:
        ostr <<"COMPLEX";
        break;
      case Type::MATRIX:
        ostr <<"MATRIX[" << *type.embeddedType() << "]";
      case Type::RANGE:
        ostr <<"RANGE";
        break;
      case Type::STRING:
        ostr <<"STRING";
        break;
      case Type::UNKNOWN:
        ostr << "UNKNOWN";
        break;
      case Type::VOID:
        ostr << "VOID";
        break;
      case Type::FUNCTION_POINTER:
        ostr << "FUNCTION_POINTER";
        break;
    }
    return ostr;
  }
}
