/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "EP_Engine.h"
#include "EP_AudioProvider.h"
#include "../media/MT_AudioStream.h"

#include "../helper/HL_Exception.h"
#include "../helper/HL_Log.h"
#include "../helper/HL_String.h"
#include "rutil/DnsUtil.hxx"
#include "resip/stack/DnsResult.hxx"
#include "resip/stack/SipStack.hxx"
#include "resip/stack/TcpBaseTransport.hxx"
#include "resip/stack/ExtensionParameter.hxx"
#include "resip/stack/MultipartRelatedContents.hxx"
#include "resip/stack/Rlmi.hxx"
#include "resip/stack/Pidf.hxx"
#include "resip/stack/ExtensionHeader.hxx"
#include "resip/stack/ssl/Security.hxx"
#include "rutil/ParseBuffer.hxx"
#include "rutil/XMLCursor.hxx"
#include "rutil/dns/RRVip.hxx"
#include "rutil/dns/QueryTypes.hxx"
#include "rutil/dns/DnsStub.hxx"
#include "resip/stack/Pidf.hxx"
#include "resip/stack/PlainContents.hxx"
#include "resip/dum/InviteSession.hxx"

#if defined(TARGET_OSX)
# include "resip/stack/ssl/MacSecurity.hxx"
#endif

#if defined(TARGET_WIN)
# include "resip/stack/ssl/WinSecurity.hxx"
#endif

#define LOG_SUBSYSTEM "engine"
#define LOCK Lock l(mGuard)
#define CAST2RESIPSESSION(x) (x.isValid() ? (x->getAppDialogSet().isValid() ? dynamic_cast<ResipSession*>(x->getAppDialogSet().get()) : NULL) : NULL)

typedef resip::SdpContents::Session::Medium Medium;
typedef resip::SdpContents::Session::MediumContainer MediumContainer;

class TransportLogger: public resip::Transport::SipMessageLoggingHandler
{
public:
    void outboundMessage(const resip::Tuple &source, const resip::Tuple &destination, const resip::SipMessage &msg) override
    {
        std::ostringstream dest_buffer; dest_buffer << destination;
        std::ostringstream msg_buffer; msg_buffer << msg;
        std::string msg_text = msg_buffer.str();
#if defined(TARGET_ANDROID)
        if (msg_text.size() > 512)
        {
            ICELogDebug(<< "Sent to " << dest_buffer.str() << " :");
            msg_text = strx::prefixLines(msg_text, "<---");

            auto lines = strx::split(msg_text);
            for (const auto& l: lines)
                ICELogDebug(<< l);
        }
        else
            ICELogDebug(<< "Sent to " << dest_buffer.str() << "\n" << strx::prefixLines(msg_text, "<---"));
#else
        ICELogDebug(<< "Sent to " << dest_buffer.str() << "\n" << strx::prefixLines(msg_text, "<---"));
#endif
    }

    // Note:  retransmissions store already encoded messages, so callback doesn't send SipMessage it sends
    //        the encoded version of the SipMessage instead.  If you need a SipMessage you will need to
    //        re-parse back into a SipMessage in the callback handler.
    void outboundRetransmit(const resip::Tuple &source, const resip::Tuple &destination, const resip::SendData &data) override
    {}

    void inboundMessage(const resip::Tuple& source, const resip::Tuple& destination, const resip::SipMessage &msg) override
    {
        std::ostringstream source_buffer; source_buffer << source;
        std::ostringstream msg_buffer; msg_buffer << msg;
        std::string msg_text = msg_buffer.str();
#if defined(TARGET_ANDROID)
        if (msg_text.size() > 512)
        {
            ICELogDebug(<< "Received from " << source_buffer.str() << " :");
            msg_text = strx::prefixLines(msg_text, "--->");
            auto lines = strx::split(msg_text);
            for (const auto& l: lines)
                ICELogDebug(<< l);
        }
        else
            ICELogDebug(<< "Received from " << source_buffer.str() << "\n" << strx::prefixLines(msg_text, "<---"));
#else
        ICELogDebug(<< "Received from " << source_buffer.str() << "\n" << strx::prefixLines(msg_buffer.str(), "--->"));
#endif
    }

};

//-------------- UserAgent -----------------------
UserAgent::UserAgent()
{
    mStack = nullptr;
    mDum = nullptr;

    mConfig[CONFIG_PRESENCE_ID] = std::string("device");
    mConfig[CONFIG_RTCP_ATTR] = true;

    // Create master profile
    mProfile = std::make_shared<resip::MasterProfile>();
    mProfile->clearSupportedMethods();
    mProfile->addSupportedMethod(resip::INVITE);
    mProfile->addSupportedMethod(resip::BYE);
    mProfile->addSupportedMethod(resip::CANCEL);
    mProfile->addSupportedMethod(resip::REGISTER);
    mProfile->addSupportedMethod(resip::NOTIFY);
    mProfile->addSupportedMethod(resip::SUBSCRIBE);
    mProfile->addSupportedMethod(resip::OPTIONS);
    mProfile->addSupportedMethod(resip::UPDATE);
    mProfile->addSupportedMethod(resip::MESSAGE);
    mProfile->addSupportedMethod(resip::ACK);
    mProfile->clearDigestCredentials();
    mProfile->unsetOutboundProxy();
}

UserAgent::~UserAgent()
{
    LOCK;

    shutdown();
    mAccountSet.clear();
    mClientObserverMap.clear();
    mServerObserverMap.clear();
    mSessionMap.clear();
}


void UserAgent::start()
{
    ICELogInfo(<< "Attempt to start endpoint.");
    LOCK;
    if (mStack)
    {
        ICELogError(<<"Endpoint is started already.");
        return;
    }

    // Initialize resip loggег
    resip::Log::initialize(resip::Log::OnlyExternal, resip::Log::Info, "Client", *this);

    // Build list of nameservers if specified
    resip::DnsStub::NameserverList nslist;

    if (mConfig.exists(CONFIG_OWN_DNS))
    {
        std::string line, t = mConfig[CONFIG_OWN_DNS].asStdString();
        std::stringstream ss(t);

        while (std::getline(ss, line))
        {
            line = strx::trim(line);
            ice::NetworkAddress addr(line.c_str(), 0); addr.setPort(80); // Fake port to make ICEAddress initialized
            switch (addr.family())
            {
            case AF_INET:   nslist.push_back(resip::GenericIPAddress(*addr.sockaddr4())); break;
            case AF_INET6:  nslist.push_back(resip::GenericIPAddress(*addr.sockaddr6())); break;
            }
        }
    }

    // Security will be deleted in stop() method (in Stack destructor)
    resip::Security* s = nullptr;
#if defined(TARGET_WIN)
    s = new resip::WinSecurity();
#elif defined(TARGET_OSX)
    s = new resip::Security();
#elif defined(TARGET_LINUX)
    s = new resip::Security("/etc/ssl/certs");
#elif defined(TARGET_ANDROID)
    s = new resip::Security();
#endif
    mStack = new resip::SipStack(s, nslist);

    // Add root cert if specified
    if (mConfig.exists(CONFIG_ROOTCERT))
    {
        try
        {
            resip::Data cert = resip::Data(mConfig[CONFIG_ROOTCERT].asStdString());
            mStack->getSecurity()->addRootCertPEM(cert);
        }
        catch(resip::BaseException& /*e*/)
        {
            ICELogError(<< "Failed to preload root certificate");
        }
    }

    mStack->setTransportSipMessageLoggingHandler(std::make_shared<TransportLogger>());

    // Add transports
    mTransportList.clear();
    resip::InternalTransport* t;

#define ADD_TRANSPORT4(X) if ((t = dynamic_cast<resip::InternalTransport*>(mStack->addTransport(X, 0, resip::V4)))) { /*t->setTransportLogger(this);*/ mTransportList.push_back(t);}
#define ADD_TRANSPORT6(X) if ((t = dynamic_cast<resip::InternalTransport*>(mStack->addTransport(X, 0, resip::V6)))) { /*t->setTransportLogger(this);*/ mTransportList.push_back(t);}

    switch (mConfig[CONFIG_TRANSPORT].asInt())
    {
    case TransportType_Any:
        if (mConfig[CONFIG_IPV4].asBool())
        {
            ADD_TRANSPORT4(resip::TCP)
            ADD_TRANSPORT4(resip::UDP)
            ADD_TRANSPORT4(resip::TLS)
        }
        if (mConfig[CONFIG_IPV6].asBool())
        {
            ADD_TRANSPORT6(resip::TCP)
            ADD_TRANSPORT6(resip::UDP)
            ADD_TRANSPORT6(resip::TLS)
        }
        break;

    case TransportType_Udp:
        if (mConfig[CONFIG_IPV4].asBool())
            ADD_TRANSPORT4(resip::UDP);
        if (mConfig[CONFIG_IPV6].asBool())
            ADD_TRANSPORT6(resip::UDP);
        break;

    case TransportType_Tcp:
        if (mConfig[CONFIG_IPV4].asBool())
            ADD_TRANSPORT4(resip::TCP);
        if (mConfig[CONFIG_IPV6].asBool())
            ADD_TRANSPORT6(resip::TCP);
        break;

    case TransportType_Tls:
        if (mConfig[CONFIG_IPV4].asBool())
            ADD_TRANSPORT4(resip::TLS);
        if (mConfig[CONFIG_IPV6].asBool())
            ADD_TRANSPORT6(resip::TLS);
        break;

    default:
        assert(0);
    }

    mDum = new resip::DialogUsageManager(*mStack);

    // Set the name of user agent
    if (mConfig[CONFIG_USERAGENT].asBool())
        mProfile->setUserAgent(mConfig[CONFIG_USERAGENT].asStdString().c_str());

    // Configure rport
    mProfile->setRportEnabled(mConfig[CONFIG_RPORT].asBool());

    // Set keep-alive packets interval
    if (mConfig.exists(CONFIG_KEEPALIVETIME))
        mProfile->setKeepAliveTimeForDatagram(mConfig[CONFIG_KEEPALIVETIME].asInt());
    else
        mProfile->setKeepAliveTimeForDatagram(30);

    int dnsCacheTime = mConfig.exists(CONFIG_DNS_CACHE_TIME) ? mConfig[CONFIG_DNS_CACHE_TIME].asInt() : 86400;
    mStack->getDnsStub().setDnsCacheSize(65536);
    mStack->getDnsStub().setDnsCacheTTL(dnsCacheTime);

    // Disable statistics
    mStack->statisticsManagerEnabled() = false;

    // Allow wildcard certificates
    mStack->getSecurity()->setAllowWildcardCertificates(true);

    // Add special body type
    mProfile->addSupportedMimeType(resip::MESSAGE, resip::Mime("text", "plain"));
    mProfile->addSupportedMimeType(resip::INVITE, resip::Mime("application", "ddp"));
    mProfile->addSupportedMimeType(resip::NOTIFY, resip::Pidf::getStaticType());
    mProfile->addSupportedMimeType(resip::NOTIFY, resip::Mime("application", "simple-message-summary"));
    mProfile->addSupportedMimeType(resip::NOTIFY, resip::Mime("message", "sipfrag"));
    mProfile->addSupportedMimeType(resip::OPTIONS, resip::Mime("application", "sdp"));

    // xcap/rls
    mProfile->addSupportedMimeType(resip::NOTIFY, resip::Mime("application", "xcap-diff+xml"));
    mProfile->addSupportedMimeType(resip::NOTIFY, resip::Mime("application", "rlmi+xml"));
    mProfile->addSupportedMimeType(resip::NOTIFY, resip::Mime("multipart", "related"));
    mProfile->addSupportedOptionTag(resip::Token("eventlist"));

    mDum->setMasterProfile(mProfile);
    mDum->setClientRegistrationHandler(this);
    mDum->setClientAuthManager(unique_ptr<resip::ClientAuthManager>(new resip::ClientAuthManager()));

    mDum->addClientSubscriptionHandler(resip::Symbols::Presence, this);
    mDum->addClientSubscriptionHandler(resip::Data("message-summary"), this);
    mDum->addServerSubscriptionHandler(resip::Symbols::Presence, this);
    mDum->addServerSubscriptionHandler("refer", this);
    mDum->addClientSubscriptionHandler("refer", this);
    mDum->addClientPublicationHandler(resip::Symbols::Presence, this);
    mDum->setInviteSessionHandler(this);
    mDum->setClientPagerMessageHandler(this);
    mDum->setServerPagerMessageHandler(this);

    unique_ptr<resip::AppDialogSetFactory> uac_dsf(new ResipSessionFactory(this));
    mDum->setAppDialogSetFactory(std::move(uac_dsf));

    // Fire onStart event if stun is not used or stun server ip is known
    if (mConfig[CONFIG_STUNSERVER_NAME].asStdString().empty() || !mConfig[CONFIG_STUNSERVER_IP].asStdString().empty())
        onStart(0);
}


void UserAgent::shutdown()
{
    if (!mStack)
        return;

    ICELogInfo( << "Attempt to stop endpoint.");

    {
        LOCK;
        for (auto& observerIter: mClientObserverMap)
            observerIter.second->stop();

        for (auto& observerIter: mServerObserverMap)
            observerIter.second->stop();

        for (auto& sessionIter: mSessionMap)
            sessionIter.second->stop();

        for (auto& accountIter: mAccountSet)
            accountIter->stop();
    }
}

bool UserAgent::active()
{
    LOCK;
    return mStack != nullptr;
}

void UserAgent::refresh()
{
    LOCK;

    for (auto& acc: mAccountSet)
        acc->refresh();

    for (auto& observer: mClientObserverMap)
        observer.second->refresh();
}

void UserAgent::onDumCanBeDeleted()
{
    delete mDum; mDum = nullptr;
    delete mStack; mStack = nullptr;

    mClientObserverMap.clear();
    mServerObserverMap.clear();

    // Generate onStop event here
    onStop();
}

void UserAgent::stop()
{
    LOCK;
    if (!mStack || !mDum)
        return;

    // Clear transport list to avoid races
    mTransportList.clear();

    // Dump statistics here
    ICELogInfo(<< "Remaining "
               << Session::InstanceCounter.load() << " session(s), "
               << ResipSession::InstanceCounter.load() << " resip DialogSet(s), "
               << resip::ClientRegistration::InstanceCounter.load() << " ClientRegistration(s)");

    mDum->shutdown(this);
    onDumCanBeDeleted();

}


void UserAgent::process()
{
    resip::FdSet fdset;
    if (mStack && mDum)
    {
        bool connectionFailed = false;

        mStack->buildFdSet(fdset);
        //unsigned int t1 = mStack->getTimeTillNextProcessMS();
        int ret = fdset.selectMilliSeconds(0);
        if (ret >= 0) // Got any results or time to send new packets?
        {
            Lock l(mGuard);
            //ICELogDebug(<< "Smth on SIP socket(s)");
            mStack->process(fdset);

            // Check if there failed connections
            for (unsigned transportIndex = 0; transportIndex < mTransportList.size(); transportIndex++)
                if (mTransportList[transportIndex]->getConnectionsDeleted())
                {
                    connectionFailed = true;
                    mTransportList[transportIndex]->resetConnectionsDeleted();
                }
        }
        
        {
            Lock l(mGuard);
            while (mDum->process())
                ;
        }

        if (connectionFailed)
            this->onSipConnectionFailed();
    }

    // Erase one terminated session. The rule is : seession must not have references from resiprocate and reference count has to be 1.
    {
        Lock l(mGuard);
        SessionMap::iterator sessionIter;
        for (sessionIter = mSessionMap.begin(); sessionIter != mSessionMap.end(); ++sessionIter)
        {
            if (sessionIter->second.use_count() == 1 && !sessionIter->second->mResipSession)
            {
                mSessionMap.erase(sessionIter);
                break;
            }
        }
    }

#pragma region ICE packet generating
    // Iterate available sessions
    {
        Lock l(mGuard);

        // Find all sessions
        std::set<int> idSet;
        for (auto sessionIter = mSessionMap.begin(); sessionIter != mSessionMap.end(); ++sessionIter)
            idSet.insert(sessionIter->first);

        // Now process session one by one checking if current is available yet
        std::set<int>::iterator resipIter;
        for (resipIter = idSet.begin(); resipIter != idSet.end(); ++resipIter)
        {
            auto sessionIter = mSessionMap.find(*resipIter);
            if (sessionIter == mSessionMap.end())
                continue;

            // Extract reference to session
            Session& session = *sessionIter->second;

            // Send queued offers if possible
            session.processQueuedOffer();

            // Generate outgoing data while available
            int iceStreamId = -1, iceComponentId = -1; void* iceTag = NULL; bool iceResponse;
            ice::PByteBuffer buffer;
            while ((buffer = session.mIceStack->generateOutgoingData(iceResponse, iceStreamId, iceComponentId, iceTag)))
            {
                // Find corresponding data provider
                for (unsigned i=0; i < session.mStreamList.size(); ++i)
                {
                    Session::Stream& stream = session.mStreamList[i];

                    if (stream.provider() && stream.iceInfo().mStreamId == iceStreamId)
                    {
                        // Send generated packet via provider's method to allow custom scheme of encryption
                        ICELogDebug(<<"Sending ICE packet to " << buffer->remoteAddress().toStdString() << " with " << buffer->comment());

                        RtpPair<PDatagramSocket>& pair = buffer->remoteAddress().family() == AF_INET6 ? stream.socket6() : stream.socket4();
                        PDatagramSocket s = iceComponentId == ICE_RTP_ID ? pair.mRtp : pair.mRtcp;
                        if (s)
                            stream.provider()->sendData(s, buffer->remoteAddress(), buffer->data(), buffer->size());
                        break;
                    }
                } // end of provider iterating
            }
        }
    }
#pragma endregion

    // TODO: see if there expired sessions - stopped and awaiting turn resources deallocation
}

void UserAgent::addRootCert(const ByteBuffer& data)
{
    LOCK;
    if (!mStack)
        return;
    resip::Data b(data.data(), data.size());
    try {
        mStack->getSecurity()->addRootCertPEM(b);
    }
    catch(...) {
        // Ignore silently
    }
}

PAccount UserAgent::createAccount(PVariantMap config)
{
    PAccount account(new Account(config, *this));
    mAccountSet.insert(account);
    return account;
}

void UserAgent::deleteAccount(PAccount account)
{
    // Delete reference from internal list
    AccountSet::iterator accountIter = mAccountSet.find(account);
    if (accountIter != mAccountSet.end())
        mAccountSet.erase(accountIter);
}

PSession UserAgent::createSession(PAccount account)
{
    LOCK;

    // Create session object. ICE stack will be created as member of DemoAppDialogSet
    PSession session(new Session(account));
    session->setUserAgent(this);

    // Save reference to session
    mSessionMap[session->id()] = session;

    // Create ICE stack and configure it
    account->prepareIceStack(session.get(), ice::RoleControlling);

    return session;
}

std::string UserAgent::formatSipAddress(const std::string& sip)
{
    std::string result;
    if (sip.size())
    {
        if (sip.find("sip:") == std::string::npos && sip.find("sips:") == std::string::npos)
            result = "<sip:" + sip + ">";
        else
            if (sip[0] != '<' && sip.find('<') == std::string::npos)
                result = "<" + sip + ">";
            else
                result = sip;
    }
    return result;
}

bool UserAgent::isSipAddressValid(const std::string& sip)
{
    bool result = false;
    try
    {
        std::string s = sip;
        if (s.find('<') == std::string::npos)
            s = "<" + s;
        if (s.find('>') == std::string::npos)
            s += ">";

        resip::Data d(formatSipAddress(s));
        resip::Uri uri(d);
        result = uri.isWellFormed();
        if (result)
        {
            if (uri.user().empty() || uri.host().empty())
                result = false;
        }
    }
    catch (...)
    {
        return result;
    }
    return result;
}

UserAgent::SipAddress UserAgent::parseSipAddress(const std::string& sip)
{
    SipAddress result;

    result.mValid = isSipAddressValid(sip);
    if (result.mValid)
    {
        resip::Data d(formatSipAddress(sip));
        resip::NameAddr nameaddr(d);
        //resip::Uri uri(d);
        if (!nameaddr.isWellFormed())
            result.mValid = false;
        else
        {
            result.mUsername = nameaddr.uri().user().c_str();
            result.mDomain = nameaddr.uri().host().c_str();
            if (nameaddr.uri().port())
            {
                char porttext[32];
                std::snprintf(porttext, sizeof(porttext), ":%u", (unsigned)nameaddr.uri().port());
                result.mDomain += porttext;
            }

            result.mScheme = nameaddr.uri().scheme().c_str();
            if (result.mScheme.find('<') == 0)
                result.mScheme.erase(0, 1);
            result.mDisplayname = nameaddr.displayName().c_str();

            result.mValid &= !result.mUsername.empty() && !result.mDomain.empty();
        }
    }
    return result;
}

bool UserAgent::compareSipAddresses(const std::string& sip1, const std::string& sip2)
{
    if (sip1.empty() || sip2.empty())
        return false;

    resip::Data d1(formatSipAddress(sip1)), d2(formatSipAddress(sip2));
    resip::Uri uri1(d1), uri2(d2);

    return uri1.getAorNoPort().uppercase() == uri2.getAorNoPort().uppercase();
}


void UserAgent::onGathered(PSession s)
{
    ICELogInfo(<< "Session " << s->sessionId() << " gathered candidates");
}

// Called when new candidate is gathered
void UserAgent::onCandidateGathered(PSession s, const char* address)
{
    ICELogInfo(<< "Session " << s->sessionId() << " gathered new candidate " << address);
}


// Called when new connectivity check is finished
void UserAgent::onCheckFinished(PSession s, const char* description)
{

}

void UserAgent::onLog(const char* msg)
{
}

void UserAgent::sendOffer(Session* session)
{
    assert(session->mResipSession);

    // Construct SDP
    resip::SdpContents sdp;
    session->buildSdp(sdp, Sdp_Offer);

    if (session->mOriginVersion == 1)
    {
        // Construct INVITE session
        auto msg = mDum->makeInviteSession(session->mRemotePeer, session->account()->mProfile, &sdp, session->mResipSession);

        // Include user headers
        for (Session::UserHeaders::const_iterator iter = session->mUserHeaders.begin(); iter != session->mUserHeaders.end(); iter++)
        {
            const std::string& name = iter->first;
            const std::string& value = iter->second;

            msg->header(resip::ExtensionHeader(name.c_str())).push_back(resip::StringCategory(value.c_str()));
        }
        mDum->send(msg);
    }
    else
    {
        // Send SDP
        resip::InviteSession* h = dynamic_cast<resip::InviteSession*>(session->mInviteHandle.get());
        if (h)
            h->provideOffer(sdp);
        else
            ICELogError(<< "No cast to InviteSession");
    }
}

#pragma region Registration handler

void UserAgent::onSuccess(resip::ClientRegistrationHandle h, const resip::SipMessage& response)
{
    ICELogInfo (<< "Registration got 200 response.");
    Lock l(mGuard);

    // Find account by registration handle
    PAccount account = getAccount(response.header(resip::h_From));
    if (account)
        account->onSuccess(h, response);
}

// Called when all of my bindings have been removed
void UserAgent::onRemoved(resip::ClientRegistrationHandle h, const resip::SipMessage& response)
{
    ICELogInfo( << "Registration is removed.");


    Lock l(mGuard);
    for (AccountSet::iterator accountIter = mAccountSet.begin(); accountIter != mAccountSet.end(); accountIter++)
        if ((*accountIter)->getUserProfile() == h->getUserProfile())
            (*accountIter)->onRemoved(h, response);
}

/// call on Retry-After failure. 
/// return values: -1 = fail, 0 = retry immediately, N = retry in N seconds
int UserAgent::onRequestRetry(resip::ClientRegistrationHandle h, int retrySeconds, const resip::SipMessage& response)
{
    return -1;
}

// Called if registration fails, usage will be destroyed (unless a 
// Registration retry interval is enabled in the Profile)
void UserAgent::onFailure(resip::ClientRegistrationHandle h, const resip::SipMessage& response)
{
    ICELogInfo (<< "Registration failed with code " << response.header(resip::h_StatusLine).statusCode());

    Lock l(mGuard);
    PAccount account = getAccount(response.header(resip::h_From));
    if (account)
        account->onFailure(h, response);
}


#pragma endregion

bool UserAgent::operator()(resip::Log::Level level,
                           const resip::Subsystem& subsystem,
                           const resip::Data& appName,
                           const char* file,
                           int line,
                           const resip::Data& message,
                           const resip::Data& messageWithHeaders,
                           const resip::Data& instanceName)
{
    std::string filename = file;
    std::stringstream ss;

    ss << "File "  << strx::extractFilename(filename).c_str() << ", line " << line << ": " << message.c_str();
    if (level <= resip::Log::Crit)
        ICELogCritical(<< ss.str())
            else
            if (level <= resip::Log::Warning)
            ICELogError(<< ss.str().c_str())
            else
            if (level < resip::Log::Debug)
            ICELogInfo(<< ss.str().c_str())
            else
            ICELogDebug(<< ss.str().c_str())
            return false;
}

#pragma region INVITE handler


/// called when an initial INVITE or the intial response to an outoing invite  
void UserAgent::onNewSession(resip::ClientInviteSessionHandle h, resip::InviteSession::OfferAnswerType oat, const resip::SipMessage& msg)
{
}

void UserAgent::onNewSession(resip::ServerInviteSessionHandle h, resip::InviteSession::OfferAnswerType oat, const resip::SipMessage& msg)
{
    ResipSession* rs = CAST2RESIPSESSION(h);
    if (!rs)
    {
        h->reject(503);
        return;
    }

    // Find account
    PAccount account = getAccount(h->myAddr());
    if (!account)
    {
        h->reject(503);
        return;
    }

    // Bring new user session
    PSession s = createSession(account);

    rs->setSession(s.get());
    mSessionMap[s->sessionId()] = s;

    // Save remote address
    s->setRemoteAddress(h->peerAddr().uri().getAor().c_str());

    ICELogInfo( << "Session " << s->sessionId() << ": incoming.");

    h->provisional(100);

    // Create ICE stack and configure it
    if (account)
        account->prepareIceStack(s.get(), ice::RoleControlled);
}

/// Received a failure response from UAS
void UserAgent::onFailure(resip::ClientInviteSessionHandle, const resip::SipMessage& msg)
{
}


/// called when an in-dialog provisional response is received that contains an SDP body
void UserAgent::onEarlyMedia(resip::ClientInviteSessionHandle h, const resip::SipMessage&, const resip::SdpContents&)
{
}


/// called when dialog enters the Early state - typically after getting 18x
void UserAgent::onProvisional(resip::ClientInviteSessionHandle h, const resip::SipMessage& msg)
{
    ResipSession* rs = CAST2RESIPSESSION(h);
    if (!rs)
        return;
    PSession s = getUserSession(rs->mSessionId);
    if (!s)
        return;

    if (msg.isResponse())
    {
        int responseCode = msg.header(resip::h_StatusLine).statusCode();
        onSessionProvisional(s, responseCode);
    }

}


/// called when a dialog initiated as a UAC enters the connected state
void UserAgent::onConnected(resip::ClientInviteSessionHandle h, const resip::SipMessage& msg)
{
    ResipSession* rs = CAST2RESIPSESSION(h);
    if (!rs)
        return;
    PSession s = getUserSession(rs->mSessionId);
    if (!s)
        return;

    if (!s->mOfferAnswerCounter)
    {
        ICELogInfo (<< "Session " << s->sessionId() << ": connected.");

        // Transfer user headers
        if (h.isValid())
            h->setUserHeaders(s->mUserHeaders);

        onSessionEstablished(s, EV_SIP, RtpPair<InternetAddress>());

        for (unsigned i=0; i<s->mStreamList.size(); i++)
        {
            if (s->mStreamList[i].provider())
                s->mStreamList[i].provider()->sessionEstablished(EV_SIP);
        }
    }
    s->mOfferAnswerCounter++;
}


/// called when a dialog initiated as a UAS enters the connected state
void UserAgent::onConnected(resip::InviteSessionHandle h, const resip::SipMessage& msg)
{
    ResipSession* resipSession = dynamic_cast<ResipSession*>(h->getAppDialogSet().get());
    if (!resipSession)
        return;
    PSession s = getUserSession(CAST2RESIPSESSION(h)->mSessionId);

    if (!s)
        return;

    ICELogInfo (<< "Session " << s->mSessionId << ": connected.");

    // Transfer user headers
    if (h.isValid())
        h->setUserHeaders(s->mUserHeaders);

    onSessionEstablished(s, EV_SIP, RtpPair<InternetAddress>());

    for (unsigned i=0; i<s->mStreamList.size(); i++)
    {
        if (s->mStreamList[i].provider())
            s->mStreamList[i].provider()->sessionEstablished(EV_SIP);
    }
}


void UserAgent::onTerminated(resip::InviteSessionHandle h, resip::InviteSessionHandler::TerminatedReason reason, const resip::SipMessage* related)
{
    ResipSession* rs = CAST2RESIPSESSION(h);
    if (!rs)
        return;
    PSession s = getUserSession(rs->mSessionId);
    if (!s)
        return;

    ICELogInfo( << "Session " << s->mSessionId << ": terminated.");

    int errorcode = 0;
    if (related)
    {
        if (related->isResponse())
            errorcode = related->header(resip::h_StatusLine).statusCode();
    }

    if (s->mResipSession)
        s->mResipSession->runTerminatedEvent(ResipSession::Type_Call, errorcode, (int)reason);
    s->clearProviders();

    // TODO: run turn resource deallocation sequence here. Otherwise release user session object.
}

/// called when a fork that was created through a 1xx never receives a 2xx
/// because another fork answered and this fork was canceled by a proxy. 
void UserAgent::onForkDestroyed(resip::ClientInviteSessionHandle)
{
}


/// called when a 3xx with valid targets is encountered in an early dialog     
/// This is different then getting a 3xx in onTerminated, as another
/// request will be attempted, so the DialogSet will not be destroyed.
/// Basically an onTermintated that conveys more information.
/// checking for 3xx respones in onTerminated will not work as there may
/// be no valid targets.
void UserAgent::onRedirected(resip::ClientInviteSessionHandle, const resip::SipMessage& msg)
{
}

/// Called when an SDP answer is received - has nothing to do with user
/// answering the call 
void UserAgent::onAnswer(resip::InviteSessionHandle h, const resip::SipMessage& msg, const resip::SdpContents& sdp)
{
    // Check if session casts ok
    ResipSession* resipSession = dynamic_cast<ResipSession*>(h->getAppDialogSet().get());
    if (!resipSession)
        return;
    Session* s = resipSession->session();
    if (!s)
        return;

    bool iceAvailable = true;

    ICELogInfo( << "Session " << s->mSessionId << ": got answer.");

    // Check for remote ICE credentials
    std::string icePwd, iceUfrag;
    if (sdp.session().exists("ice-pwd"))
        icePwd = sdp.session().getValues("ice-pwd").front().c_str();
    if (sdp.session().exists("ice-ufrag"))
        iceUfrag = sdp.session().getValues("ice-ufrag").front().c_str();

    if (s->mStreamList.size() < sdp.session().media().size())
    {
        ICELogError( << "SDP answer has wrong number of streams");
        h->end();
        return;
    }

    // Get default remote IP
    std::string remoteDefaultIP;
    if (sdp.session().isConnection())
        remoteDefaultIP = sdp.session().connection().getAddress().c_str();

    bool mediasupported = false;

    // Iterate SDP's streams
    std::list<resip::SdpContents::Session::Medium>::const_iterator mediaIter;
    unsigned streamIndex = 0;
    for (mediaIter = sdp.session().media().begin(), streamIndex = 0;
         mediaIter != sdp.session().media().end() && streamIndex < s->mStreamList.size();
         ++mediaIter, ++streamIndex)
    {
        Session::Stream& stream = s->mStreamList[streamIndex];
        const resip::SdpContents::Session::Medium& remoteStream = *mediaIter;

        // Update remote default ip if available
        const std::list<resip::SdpContents::Session::Connection>& streamConnections = remoteStream.getMediumConnections();
        if (streamConnections.size())
            remoteDefaultIP = streamConnections.front().getAddress().c_str();

        if (remoteDefaultIP.empty())
            continue;

        // Check if stream is active
        if (remoteStream.exists("inactive"))
        {
            // Move to next stream.
            // Deactivate rejected provider
            if (stream.provider())
            {
                stream.provider()->sessionTerminated(); // close corresponding media
                stream.setProvider( PDataProvider() );  // free provider
                SocketHeap::instance().freeSocketPair( stream.socket4() ); // close provider's socket ip4
                SocketHeap::instance().freeSocketPair( stream.socket6() ); // close provider's socket ip6
                s->mIceStack->removeStream( stream.iceInfo().mStreamId ); // remove stream from ice stack
            }
            continue;
        }

        // Try to parse SDP with ICE stack
        unsigned short remoteDefaultPort = remoteStream.port();

        // Extract remote ICE candidates vector
        const std::list<resip::Data> candidateList = remoteStream.getValues("candidate");
        if (candidateList.empty())
            iceAvailable = false;

        std::vector<std::string> candidateVector;
        std::list<resip::Data>::const_iterator cit = candidateList.begin();
        for (; cit != candidateList.end(); ++cit)
            candidateVector.push_back(cit->c_str());

        if (remoteStream.exists("ice-pwd"))
            icePwd = remoteStream.getValues("ice-pwd").front().c_str();
        if (remoteStream.exists("ice-ufrag"))
            iceUfrag = remoteStream.getValues("ice-ufrag").front().c_str();

        // ICEBox::processSdpOffer() may install permissions on TURN peer - so call it anyway.
        // Also it can adjust number of ice components per stream;
        // Corresponding turn allocation will be removed in this case.
        try
        {
            if (!s->mIceStack->processSdpOffer(stream.iceInfo().mStreamId, candidateVector, remoteDefaultIP, remoteDefaultPort, mConfig[CONFIG_DEFERRELAYED].asBool()))
                iceAvailable = false;
        }
        catch(...)
        {
            iceAvailable = false;
        }

        // Process media description with provider
        if (stream.provider())
        {
            if (stream.provider()->processSdpOffer( remoteStream, Sdp_Answer ))
            {
                InternetAddress addr(remoteDefaultIP, remoteDefaultPort), addr2(remoteDefaultIP, remoteDefaultPort+1);

                // See if remote stream has "rtcp" or "rtcp-mux" attributes
                if (remoteStream.exists("rtcp"))
                    addr2.setPort( strx::toInt(remoteStream.getValues("rtcp").front().c_str(), remoteDefaultPort+1) );
                else
                    if (remoteStream.exists("rtcp-mux"))
                        addr2.setPort( remoteDefaultPort );
                stream.provider()->setDestinationAddress(RtpPair<InternetAddress>(addr, addr2));
                mediasupported = true;
            }
        }
    }

    // Save session handle
    if (!s->mInviteHandle.isValid())
        s->mInviteHandle = h;

    // Get ICE credentials
    if (icePwd.size() && iceUfrag.size())
    {
        s->mIceStack->setRemotePassword(icePwd);
        s->mIceStack->setRemoteUfrag(iceUfrag);
    }
    else
        iceAvailable = false;

    // Reject session if there is no media
    if (!mediasupported)
    {
        ICELogError(<< "Session " << s->mSessionId << ": no supported media. Ending the session.");
        h->end();
        return;
    }

    // Start connectivity checks now
    ICELogInfo(<< "Session " << s->mSessionId << ": attempt to check connectivity.");

    if (iceAvailable)
        s->mIceStack->checkConnectivity();
    else
    {
        if (!mConfig[CONFIG_RELAY].asBool())
            s->mIceStack->clear();
    }

    uint64_t version = sdp.session().origin().getVersion();
    s->mRemoteOriginVersion = version;
}

/// Called when an SDP offer is received - must send an answer soon after this
void UserAgent::onOffer(resip::InviteSessionHandle h, const resip::SipMessage& msg, const resip::SdpContents& sdp)
{
    ResipSession* rs = CAST2RESIPSESSION(h);
    PSession s = rs ? getUserSession(rs->mSessionId) : PSession();
    if (!s)
    {
        h->reject(488);
        return;
    }

    bool iceAvailable = true;

    ICELogInfo(<< "Session " << s->sessionId() << ": got offer.");

    // Check if sdp includes ICE ufrag/password
    std::string icePwd, iceUfrag;
    if (sdp.session().exists("ice-pwd"))
        icePwd = sdp.session().getValues("ice-pwd").front().c_str();
    if (sdp.session().exists("ice-ufrag"))
        iceUfrag = sdp.session().getValues("ice-ufrag").front().c_str();

    //ice::Stack& ice = *s->mIceStack;

    uint64_t version = sdp.session().origin().getVersion();
    std::string remoteIp = sdp.session().connection().getAddress().c_str();
    // Default to 200: a retransmitted offer (same origin version) keeps the session.
    int code = 200;
    if ((uint64_t)-1 == s->mRemoteOriginVersion)
    {
        code = s->processSdp(version, iceAvailable, icePwd, iceUfrag, remoteIp, sdp.session().media());
    }
    else
        if (version == s->mRemoteOriginVersion)
        {
            // Timer, answer with previous SDP
            //session->processTimer();
        }
    if (version == s->mRemoteOriginVersion+1)
    {
        // Updated offer. Here we must check if ICE has to be restarted.
        code = s->processSdp(version, iceAvailable, icePwd, iceUfrag, remoteIp, sdp.session().media());
    }
    s->mRemoteOriginVersion = version;

    if (code != 200)
    {
        h->reject(code);
        return;
    }

    // Save session handle
    if (!s->mInviteHandle.isValid())
        s->mInviteHandle = h;

    // Save reference to resip session
    s->mResipSession = CAST2RESIPSESSION(h);

    if (!s->mAcceptedByEngine)
    {
        // Do not call OnNewSession for this session in future
        s->mAcceptedByEngine = true;

        // Notify about new session request
        onNewSession(s);
    }
}

/// called when an Invite w/out SDP is sent, or any other context which
/// requires an SDP offer from the user
void UserAgent::onOfferRequired(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

/// called if an offer in a UPDATE or re-INVITE was rejected - not real
/// useful. A SipMessage is provided if one is available
void UserAgent::onOfferRejected(resip::InviteSessionHandle, const resip::SipMessage* msg)
{
}

/// called when INFO message is received 
void UserAgent::onInfo(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

/// called when response to INFO message is received 
void UserAgent::onInfoSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

void UserAgent::onInfoFailure(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

/// called when MESSAGE message is received 
void UserAgent::onMessage(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

/// called when response to MESSAGE message is received 
void UserAgent::onMessageSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

void UserAgent::onMessageFailure(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

/// called when an REFER message is received.  The refer is accepted or
/// rejected using the server subscription. If the offer is accepted,
/// DialogUsageManager::makeInviteSessionFromRefer can be used to create an
/// InviteSession that will send notify messages using the ServerSubscription
void UserAgent::onRefer(resip::InviteSessionHandle, resip::ServerSubscriptionHandle, const resip::SipMessage& msg)
{
}

void UserAgent::onReferNoSub(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}


/// called when an REFER message receives a failure response 
void UserAgent::onReferRejected(resip::InviteSessionHandle, const resip::SipMessage& msg)
{
}

/// called when an REFER message receives an accepted response 
void UserAgent::onReferAccepted(resip::InviteSessionHandle, resip::ClientSubscriptionHandle, const resip::SipMessage& msg)
{
}

void UserAgent::onDnsResult(const resip::DNSResult<resip::DnsHostRecord>& result)
{
    if (result.status == 0)
    {
        resip::Data foundAddress = result.records.front().host();
        ICELogInfo( << "Success to resolve STUN/TURN address to " << foundAddress.c_str());
        mConfig[CONFIG_STUNSERVER_IP] = std::string(foundAddress.c_str());
        onStart(0);
    }
    else
    {
        ICELogError( << "Failed to resolve STUN or TURN server IP address.");
        int startCode = mConfig[CONFIG_STUNSERVER_NAME].asStdString().empty() ? 0 : 503;
        onStart(startCode);
    }
}

void UserAgent::onDnsResult(const resip::DNSResult<resip::DnsAAAARecord>& result)
{
}

void UserAgent::onDnsResult(const resip::DNSResult<resip::DnsSrvRecord>& result)
{
}

void UserAgent::onDnsResult(const resip::DNSResult<resip::DnsNaptrRecord>& result)
{
    ;
}

void UserAgent::onDnsResult(const resip::DNSResult<resip::DnsCnameRecord>& result)
{
    ;
}
#pragma endregion


#pragma region Publication presence
void UserAgent::onSuccess(resip::ClientPublicationHandle h, const resip::SipMessage& status)
{
    resip::NameAddr from = status.header(resip::h_To);
    PAccount account = getAccount(from);
    if (account)
        account->mPublication = h;
    onPublicationSuccess(account);
}

void UserAgent::onRemove(resip::ClientPublicationHandle, const resip::SipMessage& status)
{
    resip::NameAddr to = status.header(resip::h_To);
    PAccount account = getAccount(to);
    if (account)
        account->mPublication = resip::ClientPublicationHandle();
    onPublicationTerminated(account, 0);
}

void UserAgent::onFailure(resip::ClientPublicationHandle, const resip::SipMessage& status)
{
    resip::NameAddr to = status.header(resip::h_To);
    PAccount account = getAccount(to);
    if (account)
        account->mPublication = resip::ClientPublicationHandle();
    onPublicationTerminated(account, status.header(resip::h_StatusLine).statusCode());
}

int UserAgent::onRequestRetry(resip::ClientPublicationHandle, int retrySeconds, const resip::SipMessage& status)
{
    return -1;
}



void UserAgent::onPublicationSuccess(PAccount account)
{
}

void UserAgent::onPublicationTerminated(PAccount account, int code)
{
}

#pragma endregion

#pragma region Subscriptions

void UserAgent::onClientObserverStart(PClientObserver observer)
{
}

void UserAgent::onClientObserverStop(PClientObserver observer, int code)
{
}

void UserAgent::onPresenceUpdate(PClientObserver observer, const std::string& peer, bool online, const std::string& content)
{
}


#pragma endregion

#pragma region SubscriptionHandler

void UserAgent::onNewSubscription(resip::ServerSubscriptionHandle h, const resip::SipMessage& sub)
{
    ResipSession* s = CAST2RESIPSESSION(h);
    if (!s)
        return;

    // Get the event package name
    const char* event = sub.header(resip::h_Event).value().c_str();

    // Get the remote party
    const char* peer = sub.header(resip::h_From).uri().getAor().c_str();

    PAccount account = getAccount(sub.header(resip::h_From));
    PServerObserver so(new ServerObserver());
    so->mHandle = h;
    so->mPeer = peer;
    so->mPackage = event;
    so->mSessionId = s->sessionId();

    s->setRemoteAddress(peer);
    mServerObserverMap[so->mSessionId] = so;
    onServerObserverStart(so);
}

void UserAgent::onTerminated(resip::ServerSubscriptionHandle h)
{
    if (!h.isValid())
        return;
    ResipSession* s = CAST2RESIPSESSION(h);
    if (!s)
        return;

    ServerObserverMap::iterator observerIter = mServerObserverMap.find(s->sessionId());
    if (observerIter != mServerObserverMap.end())
    {
        onServerObserverStop(observerIter->second, 0);
        mServerObserverMap.erase(observerIter);
    }
}

void UserAgent::onServerObserverStart(PServerObserver observer)
{
}

void UserAgent::onServerObserverStop(PServerObserver observer, int code)
{
}

void UserAgent::onUpdate(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify)
{
    if (!h.isValid())
        return;

    ResipSession* s = CAST2RESIPSESSION(h);
    if (!s)
        return;
    ClientObserverMap::iterator observerIter = mClientObserverMap.find(s->sessionId());
    if (observerIter == mClientObserverMap.end())
    {
        h->rejectUpdate();
        h->end();
        return;
    }
    PClientObserver observer = observerIter->second;

    std::vector<resip::Data> availableContacts;
    h->acceptUpdate();

    // Find "from" header
    resip::Uri from = notify.header(resip::h_From).uri();

    // Find content
    resip::Contents* contents = notify.getContents();
    if (contents)
    {
        // Check if pidf
        if (resip::Pidf* pidf = dynamic_cast<resip::Pidf*>(contents))
        {
            resip::Data body = pidf->getBodyData();
            bool online = pidf->getSimpleStatus(&body);
            onPresenceUpdate(observer, observer->peer(), online, std::string(body.c_str(), body.size()));
        }
        else
            if (resip::MultipartRelatedContents* mr = dynamic_cast<resip::MultipartRelatedContents*>(contents))
            {
                resip::MultipartRelatedContents::Parts& parts = mr->parts();
                for( resip::MultipartRelatedContents::Parts::const_iterator i = parts.begin(); i != parts.end();  ++i)
                {
                    resip::Contents* c = *i;
                    assert( c );
                    resip::Mime m = c->getType();
                    if (resip::Rlmi* rlmi = dynamic_cast<resip::Rlmi*>(c))
                    {
                        resip::Data d = rlmi->getBodyData();
                        // Workaround for XMLCursor bug
                        if (d.find(resip::Data("<resource")) == resip::Data::npos)
                            continue;

                        resip::ParseBuffer b(d);
                        resip::XMLCursor c(b);
                        resip::Data tag = c.getTag();
                        if (tag != "list")
                        {
                            ICELogError( << "Failed to find <list> tag in rlmi");
                        }
                        else
                        {
                            for (bool hasRecord = c.firstChild(); hasRecord; hasRecord = c.nextSibling())
                            {
                                if (c.getTag() != "resource")
                                {
                                    ICELogError( << "Failed to find <resource> tag in rlmi");
                                }
                                else
                                {
                                    // Check if c has <instance>

                                    resip::XMLCursor::AttributeMap::const_iterator attrIter = c.getAttributes().find("uri");
                                    if (attrIter != c.getAttributes().end())
                                    {
                                        // Save uri
                                        resip::Data uri = attrIter->second;

                                        // Check if there is <instance> tag
                                        bool instance = false;
                                        for (bool hasChild = c.firstChild(); hasChild; hasChild = c.nextSibling())
                                            instance |= c.getTag() == "instance";
                                        c.parent();

                                        // Save result
                                        if (instance)
                                            availableContacts.push_back( attrIter->second );
                                    }
                                }
                            }
                        }
                    }
                    else
                        if (resip::Pidf* pidf = dynamic_cast<resip::Pidf*>(c))
                        {
                            resip::Data body = pidf->getBodyData();
                            bool online = pidf->getSimpleStatus(&body);
                            resip::Data entity = pidf->getEntity().getAorNoPort();
                            if (entity.find("sip:") == resip::Data::npos && entity.find("sips:") == resip::Data::npos)
                                entity = resip::Data("sip:") + entity;
                            onPresenceUpdate(observer, entity.c_str(), online, std::string(body.c_str(), body.size()));

                            // Drop corresponding record from availableContacts
                            std::vector<resip::Data>::iterator ci = std::find(availableContacts.begin(), availableContacts.end(), entity);
                            if (ci != availableContacts.end())
                                availableContacts.erase(ci);
                        }
                }
            }
    }

    for (unsigned i=0; i<availableContacts.size(); i++)
        onPresenceUpdate(observer, availableContacts[i].c_str(), false, std::string());
}

void UserAgent::onUpdatePending(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify, bool outOfOrder)
{
    onUpdate(h, notify);
}

void UserAgent::onUpdateActive(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify, bool outOfOrder)
{
    onUpdate(h, notify);
}

void UserAgent::onUpdateExtension(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify, bool outOfOrder)
{
    onUpdate(h, notify);
}

int UserAgent::onRequestRetry(resip::ClientSubscriptionHandle, int retrySeconds, const resip::SipMessage& notify)
{
    return -1;
}

//subscription can be ended through a notify or a failure response.
void UserAgent::onTerminated(resip::ClientSubscriptionHandle h, const resip::SipMessage* msg)
{
    // TODO - check for refer notication
    if (!h.isValid() || !msg)
        return;

    ResipSession* rs = CAST2RESIPSESSION(h);
    if (!rs)
        return;

    // Find the response code if available
    int code = 0;
    if (msg->isResponse())
        code = msg->header(resip::h_StatusLine).statusCode();

    // Remove subscription from list, call terminated event
    rs->runTerminatedEvent(ResipSession::Type_Subscription, code, 0);
}

void UserAgent::onNewSubscription(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify)
{
    if (!h.isValid())
        return;

    // Find dialog set
    ResipSession* s = CAST2RESIPSESSION(h);
    if (!s)
        return;

    if (!s->mOnWatchingStartSent)
    {
        s->mOnWatchingStartSent = true;
        ClientObserverMap::iterator observerIter = mClientObserverMap.find(s->sessionId());
        if (observerIter != mClientObserverMap.end())
            onClientObserverStart(observerIter->second);
    }
}

/// called to allow app to adorn a message.
void UserAgent::onReadyToSend(resip::ClientSubscriptionHandle, resip::SipMessage& msg)
{
}

void UserAgent::onNotifyNotReceived(resip::ClientSubscriptionHandle)
{
}

/// Called when a TCP or TLS flow to the server has terminated.  This can be caused by socket
/// errors, or missing CRLF keep alives pong responses from the server.
//  Called only if clientOutbound is enabled on the UserProfile and the first hop server 
/// supports RFC5626 (outbound).
/// Default implementation is to re-form the subscription using a new flow
void UserAgent::onFlowTerminated(resip::ClientSubscriptionHandle)
{
}
#pragma endregion

#pragma region PagerHandler
void UserAgent::onSuccess(resip::ClientPagerMessageHandle h, const resip::SipMessage& status)
{
    if (!h.isValid())
        return;
    ResipSession* s = CAST2RESIPSESSION(h);
    if (!s)
        return;
    onMessageSent(getAccount(status.header(resip::h_From)), s->sessionId(), s->remoteAddress(), s->tag());
}

void UserAgent::onFailure(resip::ClientPagerMessageHandle h, const resip::SipMessage& status, std::unique_ptr<resip::Contents> contents)
{
    if (!h.isValid())
        return;
    ResipSession* s = CAST2RESIPSESSION(h);
    if (!s)
        return;
    onMessageFailed(getAccount(status.header(resip::h_From)), s->sessionId(), s->remoteAddress(), status.header(resip::h_StatusLine).statusCode(), s->tag());
}

void UserAgent::onMessageArrived(resip::ServerPagerMessageHandle h, const resip::SipMessage& message)
{
    if (!h.isValid())
        return;
    resip::NameAddr from = message.header(resip::h_From);
    std::string peer(from.uri().getAor().c_str());
    PAccount account = getAccount(from);
    h->send(h->accept());
    resip::Contents* c = message.getContents();
    if (!c)
        onMessageArrived(account, peer, NULL, 0);
    else
    {
        resip::Data d = c->getBodyData();
        onMessageArrived(account, peer, d.c_str(), d.size());
    }
}

void UserAgent::updateInterfaceList()
{
    //ICEImpl::ICENetworkHelper::instance().reload();
}

void UserAgent::onMessageArrived(PAccount /*account*/, const std::string& /*peer*/, const void* /*ptr*/, unsigned /*length*/)
{
}

void UserAgent::onMessageFailed(PAccount /*account*/, int /*id*/, const std::string& /*peer*/, int /*code*/, void* /*tag*/)
{
}

void UserAgent::onMessageSent(PAccount /*account*/, int /*id*/, const std::string& /*peer*/, void* /*tag*/)
{
}

VariantMap& UserAgent::config()
{
    return mConfig;
}

/*static void splitToHeaders(resip::Data& content, std::vector<resip::Data>& output)
{
  resip::Data::size_type startLine = 0, endLine = content.find("\r\n");
  while (endLine != resip::Data::npos)
  {
    output.push_back(content.substr(startLine, endLine));
    
    startLine = endLine + 2;
    endLine = content.find("\r\n", startLine);
  }
  if (0 == startLine)
    output.push_back(content);
}*/

/*static bool parseHeader(resip::Data& input, resip::Data& name, resip::Data& value)
{
  resip::Data::size_type p = input.find(":");
  if (p == resip::Data::npos)
  {
    name = input;
    value.clear();
  }
  else
  {
    name = input.substr(0, p);
    value = input.substr(p+1, input.size() - p - 1);
    
    // Trim leading whitespace
    if (value.size())
    {
      if (value.at(0) == ' ')
        value = value.substr(1, value.size()-1);
    }
  }
  return true;
}*/

void UserAgent::onSipMessage(int flow, const char* msg, unsigned int length, const sockaddr* addr, unsigned int addrlen)
{
    std::string d(msg, length);
    ice::NetworkAddress address(*addr, addrlen);
    std::string addressText = address.toStdString();

    /*switch (flow)
  {
  case resip::InternalTransport::TransportLogger::Flow_Received:
    ICELogDebug(<< "Received from " << addressText << ":" << "\n"
                        << strx::prefixLines(d, "--->"));
    break;

  case resip::InternalTransport::TransportLogger::Flow_Sent:
    ICELogDebug(<< "Sent to " << addressText << "\n" << strx::prefixLines(d, "<---"));
    break;
  }*/
}

PSession UserAgent::getUserSession(int sessionId)
{
    SessionMap::iterator sessionIter = mSessionMap.find(sessionId);
    if (sessionIter != mSessionMap.end())
        return sessionIter->second;
    else
        return PSession();
}

PAccount UserAgent::getAccount(const resip::NameAddr& myAddr)
{
    PAccount acc;
    for (AccountSet::iterator accountIter = mAccountSet.begin(); accountIter != mAccountSet.end() && !acc; accountIter++)
        if ((*accountIter)->isResponsibleFor(myAddr))
            acc = *accountIter;

    return acc;
}

PAccount UserAgent::getAccount(Account* account)
{
    PAccount acc;
    for (AccountSet::iterator accountIter = mAccountSet.begin(); accountIter != mAccountSet.end() && !acc; accountIter++)
        if (accountIter->get() == account)
            acc = *accountIter;

    return acc;
}

PAccount UserAgent::getAccount(int sessionId)
{
    auto profileIter = std::find_if(mAccountSet.begin(), mAccountSet.end(), [=](const AccountSet::value_type& v) {if (v->mRegistration) return v->mRegistration->sessionId() == sessionId; else return false;});
    return (profileIter != mAccountSet.end()) ? *profileIter : PAccount();
}

#pragma endregion
