/* Copyright (c) 2015  Gerald Knizia
 * 
 * This file is part of the IboView program (see: http://www.iboview.org)
 * 
 * IboView 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, version 3.
 * 
 * IboView 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 details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with bfint (LICENSE). If not, see http://www.gnu.org/licenses/
 * 
 * Please see IboView documentation in README.txt for:
 * -- A list of included external software and their licenses. The included
 *    external software's copyright is not touched by this agreement.
 * -- Notes on re-distribution and contributions to/further development of
 *    the IboView software
 */

#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <stdexcept>
#include <cctype> // for tolower()

#ifdef QT_CORE_LIB
   #include <QString>
   #include <QByteArray>
   #include <QFile>
   #include <QApplication>
   #include <QClipboard>
#endif

// #include "CtCommon.h"
// #include "CtConstants.h"
// #define USE_INDENT_STREAM
#ifdef USE_INDENT_STREAM
   #define INDENTSTREAM_IMPL
   #include "CxIndentStream.h"
   namespace ct {
      static fmt::FIndentStream1
         s_OutputStreamAdp(std::cout,true,1);
      std::ostream
         &xout = s_OutputStreamAdp.stream,
         &xerr = std::cerr;
   } // namespace ct
#else
   namespace ct {
      std::ostream
         &xout = std::cout,
         &xerr = std::cerr;
   } // namespace ct
#endif // USE_INDENT_STREAM

#include "CtIo.h"


namespace ct {

char const
   *pResultFmt = " %-32s%18.12f\n",
   *pResultFmtAnnoted = " %-32s%18.12f  (%s)\n",
   *pResultFmtI = " %-32s%5i\n",
   *pResultFmtIAnnoted = " %-32s%5i  (%s)\n",
   *pTimingFmt = " Time for %s:%40t%10.2f sec\n",
   *pTimingPerFmt = " Time per %s:%40t%10.2f sec  (n=%i)\n";
//    *pTimingFmt = " Time for %s:%40t%10.4f sec\n",
//    *pTimingPerFmt = " Time per %s:%40t%10.4f sec  (n=%i)\n";

int
   Verbosity = 0;

void FatalError( std::string const &Message,
    char const *pFromWhere, int nLine )
{
   std::stringstream str;
   str << Message;
   if ( pFromWhere ) {
      str << " at " << pFromWhere << ":" << nLine;
   };
   throw std::runtime_error(str.str());
}



// makes a string to be output into the log as the first line of a major
// program component.
void MajorProgramIntro( std::ostream &out, const std::string &Name, const std::string &Version )
{
//     out << fmt::unind();
    out << "\n*** " << Name;
    if ( Version != "" )
        out << " [Ver. " << Version << "]";
    out << " ***\n" << std::endl;
//     out << fmt::eind();
}

// makes a string lower case. Will break with localized multibyte characters,
// but we don't such.
std::string tolower( std::string const &in )
{
    std::string
        Result(in);
    std::string::iterator
        it;
    for( it = Result.begin(); it != Result.end(); ++it )
        *it = std::tolower(*it);
    return Result;
}

// returns string 'in' with all spaces (but not tabs) removed.
std::string stripwhitespace( std::string const &in )
{
    std::string
        Result;
    Result.reserve( in.size() );
    for ( size_t i = 0; i < in.size(); ++i )
        if ( ' ' != in[i] )
            Result.push_back( in[i] );
    return Result;
}

// will load an entire file into the stringstream str. Returns false if failed.
bool LoadFileIntoMemory( TArray<char> &pFileContent,
        std::string const &FileName, unsigned *pFileLength )
{
//    std::cout << "in LoadFileIntoMemory. Name: '" << FileName << "'." << std::endl;
#ifdef QT_CORE_LIB
   // Use QT functions to load the file. This gives us access to files
   // stored in resources and the application clipboard.
   QByteArray
      Text;
   if (FileName == ":/!clipboard!") {
      QClipboard
         *clipboard = QApplication::clipboard();
      QString
         Subtype = "plain",
         sText = clipboard->text(Subtype);
      Text = sText.toUtf8();
   } else {
      QFile
         StyleFile(QString(FileName.c_str()));
      if (!StyleFile.open(QFile::ReadOnly))
         return false;
      Text = StyleFile.readAll();
   }
   pFileContent.resize(Text.size() + 2);
   // 0-terminate the string.
   pFileContent[Text.size()] = 0;
   pFileContent[Text.size()+1] = 0;
   memcpy(&pFileContent[0], Text.data(), Text.size());
   if (pFileLength)
      *pFileLength = Text.size();
   return true;
#else
    // read the entire file into an stringstream object.
    std::ifstream
        File( FileName.c_str() );
    std::size_t
        FileLength;
    if ( false == File.good() )
        return false;
    File.seekg( 0, std::ios::end );
    FileLength = File.tellg();
    if ( 0 != pFileLength )
        *pFileLength = FileLength;
    pFileContent.resize(2 + FileLength);
    memset( &pFileContent[0], 0, 2 + FileLength );
    File.seekg( 0, std::ios::beg );
    File.read( &pFileContent[0], FileLength );
    return true;
#endif
}


FLogError::FLogError(std::string const &Reason)
   : FBase(Reason)
{}


FLog::~FLog()
{
}


void FLog::WriteResult(fmt::BasicStringRef<Char> Name, double fValue)
{
   w.write(" {:<32}{:18.12f}\n", Name, fValue);
   Flush();
}

void FLog::WriteResult(fmt::BasicStringRef<Char> Name, double fValue, fmt::BasicStringRef<Char> Annotation)
{
   w.write(" {:<32}{:18.12f}  ({})\n", Name, fValue, Annotation);
   Flush();
}

void FLog::WriteCount(fmt::BasicStringRef<Char> Name, ptrdiff_t iValue)
{
   w.write(" {:<32}{:5}\n", Name, iValue);
   Flush();
}

void FLog::WriteCount(fmt::BasicStringRef<Char> Name, ptrdiff_t iValue, fmt::BasicStringRef<Char> Annotation)
{
   w.write(" {:<32}{:5}  ({})\n", Name, iValue, Annotation);
   Flush();
}

void FLog::WriteTiming(fmt::BasicStringRef<Char> Name, double fTimeInSeconds)
{
   w.write("{:<40}{:10.2f} sec\n", fmt::format(" Time for {}:", Name), fTimeInSeconds);
   Flush();
}

void FLog::WriteTiming(fmt::BasicStringRef<Char> Name, double fTimeInSeconds, size_t nTasks)
{
   w.write("{:<40}{:10.2f} sec  (n={})\n", fmt::format(" Time per {}:", Name), fTimeInSeconds/double(nTasks), nTasks);
   Flush();
}

void FLog::WriteProgramIntro(fmt::BasicStringRef<Char> Name, fmt::BasicStringRef<Char> Version)
{
   w << "\n*** " << Name;
   if (Version.size() != 0u)
      w << " [Ver. " << Version << "]";
   w << " ***\n\n";
   Flush();
}

FLog::FRunStatus FLog::GetStatus()
{
   // do nothing. derived classes might do more involved things.
   return STATUS_Okay;
}

void FLog::CheckStatus()
{
   // do nothing. derived classes might, however.
   FRunStatus
      Status = GetStatus();
   if (Status == STATUS_Okay)
      return;
   if (Status == STATUS_AbortSignalled)
      throw ct::FLogError("Abort signalled.");
   throw ct::FLogError("FLog signalled unrecognized error.");
}


FLogStdStream::FLogStdStream(std::ostream &TargetStream)
   : m_TargetStream(TargetStream)
{
}

void FLog::EmitWarning(fmt::BasicStringRef<Char> Name)
{
   w << " WARNING: " << Name << "\n";
   Flush();
}
void FLog::EmitError(fmt::BasicStringRef<Char> Name)
{
   w << " ERROR: " << Name << "\n";
   Flush();
}


void FLogStdStream::Flush()
{
   m_TargetStream << w.c_str();
   w.clear();
   m_TargetStream.flush();
}


} // namespace ct


// c/p'd from http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
#include <string.h> // for strlen

namespace ct {

// trim from left
void TrimLeft(std::string &s) {
   s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
}

// trim from right
void TrimRight(std::string &s) {
   s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}

// trim from both ends
void Trim(std::string &s) {
   TrimLeft(s);
   TrimRight(s);
}

bool StartsWith(std::string const &s, std::string const &prefix) {
   return 0 == s.compare(0, prefix.size(), prefix);
}

bool StartsWith(std::string const &s, char const *prefix) {
   return 0 == s.compare(0, strlen(prefix), prefix);
}

bool StartsWith(std::string const &s, char prefix) {
   if (s.empty()) return false;
   return s[0] == prefix;
}

void StripLineComment(std::string &s, char const *prefix)
{
   size_t iPos = s.find(prefix);
   if (iPos == std::string::npos)
      return; // not in there.
   // strip off everything after the comment indicator, and then whitespace to the right.
   s.resize(iPos);
   TrimRight(s);
}



} // namespace ct
