/*! @file

    @id $Id$
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#ifndef __CRYPTAUX_HXX__
#define __CRYPTAUX_HXX__

#include <string>
#include <sstream>
#include <iomanip>
#include <stdexcept>
#include <algorithm>

/*! @defgroup gcrypto Auxiliary Crypto-Functions
    
    Auxiliary often used funcions in cryptographic environment, such
    as logging, converting binary from and to hexadecimal or creating
    readable texts from binary data. */
//@{

#define CRYPTOLOG_QUOTE(X) CRYPTOLOG_QUOTE2(X)
#define CRYPTOLOG_QUOTE2(X) #X
#if __GNUC__ >= 2
#  define CRYPTOLOG_END " -- "<<__PRETTY_FUNCTION__<<std::endl
#else
#  define CRYPTOLOG_END std::endl
#endif

// Logging, enable with -DDEBUG
#ifndef CRYPTOLOG
#  ifndef DEBUG
#    define CRYPTOLOG(X)
#  else
#    include <iostream>
#    define CRYPTOLOG(X) {                                              \
    std::string __C_FILE__(__FILE__);                                   \
    std::string __C_LINE__(CRYPTOLOG_QUOTE(__LINE__));                  \
    std::string::size_type __LOC_POS__(__C_FILE__.rfind('/'));          \
    if (__LOC_POS__!=std::string::npos)                                 \
      __C_FILE__=__C_FILE__.substr(__LOC_POS__+1);                      \
    std::string __LOC_SPC1__(18>__C_FILE__.size()                       \
                             ?std::string(18-__C_FILE__.size(), ' ')    \
                             :std::string());                           \
    std::string __LOC_SPC2__(4>__C_LINE__.size()                        \
                             ?std::string(4-__C_LINE__.size(), ' ')     \
                             :std::string());                           \
    std::clog<<"CRYPTO: "<<__LOC_SPC1__<<__C_FILE__                     \
             <<':'<<__LOC_SPC2__<<__C_LINE__<<" -- "<<X                 \
             <<CRYPTOLOG_END;                                           \
  }
#  endif
#endif

// Verbose logging, use with care, will also log PINs.
// Enable with -DDEBUG -DVERBOSE
#ifndef CRYPTOLOG_VERBOSE
#  if !(defined(DEBUG)&&defined(VERBOSE))
#    define CRYPTOLOG_VERBOSE(X)
#  else
#    include <iostream>
#    define CRYPTOLOG_VERBOSE(X) {                                      \
    std::string __C_FILE__(__FILE__);                                   \
    std::string __C_LINE__(CRYPTOLOG_QUOTE(__LINE__));                  \
    std::string::size_type __LOC_POS__(__C_FILE__.rfind('/'));          \
    if (__LOC_POS__!=std::string::npos)                                 \
      __C_FILE__=__C_FILE__.substr(__LOC_POS__+1);                      \
    std::string __LOC_SPC1__(18>__C_FILE__.size()                       \
                             ?std::string(18-__C_FILE__.size(), ' ')    \
                             :std::string());                           \
    std::string __LOC_SPC2__(4>__C_LINE__.size()                        \
                             ?std::string(4-__C_LINE__.size(), ' ')     \
                             :std::string());                           \
    std::clog<<"CRYPTO: "<<__LOC_SPC1__<<__C_FILE__<<':'                \
             <<__LOC_SPC2__<<__C_LINE__<<" -- "<<X                      \
             <<CRYPTOLOG_END;                                           \
  }
#  endif
#endif

namespace pcsc {
  /// Get library version identification
  std::string version();
}

//! @ref gcrypto @copydoc gcrypto
namespace crypto {
  
  static const std::string LETTER_CHARS
      ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
  static const std::string NUMBER_CHARS
      ("0123456789");
  //! Contains @c @ in addition to standard characters.
  static const std::string GRAFIC_CHARS
      ("!\"#%&'()*+,-./:;<=>?[\\]^_{|}~@");
  static const std::string BLANK_CHARS
      (" ");
  static const std::string VALID_CHARS
      (LETTER_CHARS+NUMBER_CHARS+GRAFIC_CHARS+BLANK_CHARS);
  
  inline std::string hex(const std::string& data) {
    std::stringstream res;
    for (std::string::const_iterator it(data.begin()); it!=data.end(); ++it)
      res<<std::hex<<std::setfill('0')<<std::setw(2)
         <<(unsigned int)(unsigned char)*it;
    return res.str();
  }
  
  inline std::string hexTxt(const std::string& data,
                            std::string::size_type len=20,
                            std::string::size_type indent=0) {
    std::stringstream res;
    std::string::size_type pos(0);
    for (std::string::const_iterator it(data.begin()); it!=data.end(); ++it) {
      if (pos%len==0) res<<std::string(indent, ' ');
      res<<std::hex<<std::setfill('0')<<std::setw(2)
         <<(unsigned int)(unsigned char)*it;
      ++pos;
      if (pos%len==0 || pos==data.size()) {
        res<<std::string(2*(len-(pos-1)%len), ' ');
        for (std::string::size_type i(pos-(pos-1)%len-1); i<pos; ++i)
          res<<(VALID_CHARS.find(data[i])==std::string::npos?'.':data[i]);
        if (pos!=data.size()) res<<std::endl;
      }
    }
    return res.str();
  }

  inline std::string readable(const std::string& data,
                              std::string::size_type len=20,
                              std::string::size_type indent=0) {
    if (!data.size())
      return std::string(indent, ' ')+"<empty>";
    else if (data.find_first_not_of(VALID_CHARS)<data.size())
      return hexTxt(data, len, indent);
    else
      return std::string(indent, ' ')+"\""+data+"\"";
  }

  //! Convert binary to readable hexadecimal
  inline std::string binToHex(char c) {
    std::stringstream ss;
    ss<<std::hex<<std::setw(2)<<std::setfill('0')
      <<(unsigned int)(unsigned char)(c);
    return ss.str();
  }

  //! Convert binary to readable hexadecimal
  inline std::string binToHex(const std::string& s) {
    std::string result;
    for (std::string::const_iterator c(s.begin()); c!=s.end(); ++c)
      result += binToHex(*c);
    return result;
  }
  
  //! Convert readable lowercase hexadecimal to binary
  inline std::string hexToBin(std::string s) {
    const static std::string HEX("0123456789abcdef");
    std::transform(s.begin(), s.end(), s.begin(), ::tolower);
    if (s.size()%2)
      throw std::runtime_error
        ("input string \""+s
         +"\" must have an even number of hexadecimal numbers");
    if (s.find_first_not_of(HEX)!=std::string::npos)
      throw std::runtime_error
        ("input string \""+s+"\" has non hexadecimal value at position "
         +((std::stringstream&)(std::stringstream()
                                <<s.find_first_not_of(HEX))).str());
    std::string res;
    for (std::string::const_iterator it(s.begin()); it!=s.end(); ++it) {
      std::string::size_type c(HEX.find(*it));
      if (++it!=s.end()) (c <<= 4) += HEX.find(*it);
      res += std::string(1, (std::string::value_type)c);
    }
    return res;
  }

  /// convert integer to binary of given size
  inline std::string toBinary(unsigned long data, int bytes=2) {
    std::string res(bytes, '\0');
    for (int i(0); i<bytes; ++i) {
      res[i] = std::string::value_type(data&0xff);
      data>>=8;
    }
    return res;
  }
  
  /// convert integer from binary of given size
  inline unsigned long ulongFromBinary(const std::string& data) {
    unsigned long res(0);
    for (std::string::const_iterator it(data.begin()); it!=data.end(); ++it)
      (res<<=8)+=(unsigned long)(unsigned char)*it;
    return res;
  }
  
}

//@}

#endif
