#include <algorithm>
#include <string>
#include <regex>

#include <openssl/md5.h>

#include "LibSip_Steps.hh"

#include "loggers.hh"

#include "converter.hh"

namespace LibSip__Steps {

  int calculate_md5(const std::string& p_data, std::string& p_md5)
  {
    loggers::get_instance().log(">>> calculate_md5: %s", p_data.c_str());

    MD5_CTX md5_context;
    ::MD5_Init(&md5_context);
    
    loggers::get_instance().log("calculate_md5: length=%d", p_data.length());
    ::MD5_Update(&md5_context, p_data.c_str(), p_data.length()); // TODO Add support of block size > 512
    
    const size_t digestlen = 16;
    unsigned char digest[digestlen];
    ::MD5_Final(digest, &md5_context);
    p_md5 = converter::get_instance().bytes_to_hexa(std::vector<unsigned char>(digest, digest + digestlen));
    
    loggers::get_instance().log("<<< calculate_md5: %s", p_md5.c_str());
    return 0;
  }
  
  CHARSTRING fx__rndStr() {
    static const char alphanum[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    static int length = 10;
    char s[length] = { 0 };
    for (int i = 0; i < length - 1; ++i) {
      s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
    } // End of 'for' statement
    return CHARSTRING(s);
  };

  CHARSTRING fx__putInLowercase(const CHARSTRING& p__string) {
    std::string s(static_cast<const char*>(p__string));
    std::transform(s.begin(), s.end(), s.begin(), ::tolower);
    return CHARSTRING(s.c_str());
  };
  
  CHARSTRING fx__getIpAddr(const CHARSTRING& p__host__name) { return CHARSTRING(""); };
  
  CHARSTRING fx__calculateDigestResponse(const CHARSTRING& p__nonce, const CHARSTRING& p__cnonce, const CHARSTRING& p__user, const CHARSTRING& p__realm, const CHARSTRING& p__passwd, const CHARSTRING& p__alg, const CHARSTRING& p__nonceCount, const CHARSTRING& p__method, const CHARSTRING& p__qop, const CHARSTRING& p__URI, const CHARSTRING& p__HEntity) {
    loggers::get_instance().log(">>> fx__calculateDigestResponse");

    // Buid a1=USERNAME:REALM:PASSWORD
    std::string a1(static_cast<const char*>(p__user));
    a1 += ":";
    a1 += static_cast<const char*>(p__realm);
    a1 += ":";
    a1 += static_cast<const char*>(p__passwd);
    if (a1.find("\"") != std::string::npos) {
      a1 = std::regex_replace(a1, std::regex("\""), "");
    }
    loggers::get_instance().log("fx__calculateDigestResponse: a1='%s'", a1.c_str());
    std::string md5_a1;
    calculate_md5(a1, md5_a1);
    
    // Buid a2=METHOD:DIGESTURI
    std::string a2(static_cast<const char*>(p__method));
    a2 += ":";
    a2 += static_cast<const char*>(p__URI);
    if (a2.find("\"") != std::string::npos) {
      a2 = std::regex_replace(a2, std::regex("\""), ""); // TODO Move to converter
    }
    loggers::get_instance().log("fx__calculateDigestResponse: a2='%s'", a2.c_str());
    std::string md5_a2;
    calculate_md5(a2, md5_a2);
    
    // Buid resp=HA1:NONCE:HA2
    std::string resp(md5_a1);
    resp += ":";
    resp += static_cast<const char*>(p__nonce);
    resp += ":";
    resp += md5_a2;
    loggers::get_instance().log("fx__calculateDigestResponse: resp='%s'", resp.c_str());
    std::string md5_resp;
    calculate_md5(resp, md5_resp);

    // Convert into Base64
    std::vector<unsigned char> u = converter::get_instance().string_to_bytes(md5_resp);
    md5_resp = converter::get_instance().bytes_to_string(converter::get_instance().buffer_to_base64(u));
    
    loggers::get_instance().log("<<< fx__calculateDigestResponse: '%s'", md5_resp.c_str());
    return CHARSTRING(md5_resp.c_str());
  };
  
  CHARSTRING fx__encodeBase64(const CHARSTRING& p__value) {
    std::vector<unsigned char> s(static_cast<const char*>(p__value), static_cast<const char*>(p__value) + p__value.lengthof());
    std::vector<unsigned char> o = converter::get_instance().buffer_to_base64(s);
    return CHARSTRING(o.size(), (const char*)o.data());
  };
  
  CHARSTRING fx__decodeBase64(const CHARSTRING& p__value) {
    std::vector<unsigned char> s(static_cast<const char*>(p__value), static_cast<const char*>(p__value) + p__value.lengthof());
    std::vector<unsigned char> o = converter::get_instance().base64_to_buffer(s);
    return CHARSTRING(o.size(), (const char*)o.data());
  };
  
} // End of namespace LibSip__Steps
