/*---------------------------------------------------------------------------*/
/* url_handler.cpp                                                           */
/* copyright (c) innovaphone 2021                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"
#include "common/os/iomux.h"
#include "common/interface/socket.h"
#include "common/interface/url_handler.h"
#include "common/interface/notifications-if.h"
#include "common/interface/launcherapi.h"
#include "common/interface/signaling_app.h"
#include "common/ilib/json.h"
#include "common/ilib/str.h"


#include "url_handler.h"

/*---------------------------------------------------------------------------*/
/* class LocalListener                                                       */
/*---------------------------------------------------------------------------*/

void LocalListener::SocketBindResult(ISocket * const socket, const char * localAddr, word localPort)
{
    urlHandler->LocalSocketBindResult(socket, localAddr, localPort);
}

void LocalListener::SocketListenResult(ISocket * const socket, const char * remoteAddr, word remotePort)
{
    urlHandler->LocalSocketListenResult(socket, remoteAddr, remotePort);
}

void LocalListener::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    urlHandler->LocalSocketShutdown(socket, reason);
}

void LocalListener::SocketRecvCanceled(ISocket * const socket, void * buf)
{
    urlHandler->LocalSocketRecvCanceled(socket, buf);
}

/*---------------------------------------------------------------------------*/
/* class UrlHandler                                                          */
/*---------------------------------------------------------------------------*/

UrlHandler::UrlHandler(IIoMux * const iomux, class ISocketProvider * localSocketProvider, const char * urlHandlerLocalAddr) :
    localListener(this)
{
    this->iomux = iomux;
    this->localSocketProvider = localSocketProvider;
    this->launcherApi = NULL;
    this->signalingApp = NULL;

    this->stopping = false;
    iomux->RegisterShutdownHandler(this);
    this->socket = 0;
    if (localSocketProvider && urlHandlerLocalAddr) {
        this->socket = localSocketProvider->CreateSocket(iomux, &localListener, this, false);
        debug->printf("Local socket: bind to %s", urlHandlerLocalAddr);
        this->socket->Bind(urlHandlerLocalAddr);
    }
}

UrlHandler::~UrlHandler()
{
}

void UrlHandler::Shutdown()
{
    debug->printf("UrlHandler::Shutdown");
    TryStop();
}

void UrlHandler::ShutdownTimeout()
{
    debug->printf("UrlHandler::ShutdownTimeout %d %d",
        (socket != NULL), !localConnections.empty());
}

void UrlHandler::TryStop()
{
    if (!this->stopping) {
        this->stopping = true;
        if (socket) socket->Shutdown();
        for (LocalConnections::iterator i = localConnections.begin(); i != localConnections.end(); i++) {
            (*i)->Close();
        }
    }
    if (!socket && localConnections.empty()) {
        iomux->ShutdownComplete(this);
    }
}

void UrlHandler::LocalSocketBindResult(ISocket * const socket, const char * localAddr, word localPort)
{
    debug->printf("UrlHandler(%p)::SocketBindResult Local socket: bound to %s", this, localAddr);
    this->socket->Listen();
}

void UrlHandler::LocalSocketListenResult(ISocket * const socket, const char * remoteAddr, word remotePort)
{
    if (stopping) {
        debug->printf("UrlHandler(%p)::SocketListenResult Local socket: reject incoming connection from %s because service is unavailable", this, remoteAddr);
        this->socket->Accept(0);
    }
    else {
        debug->printf("UrlHandler(%p)::SocketListenResult Local socket: incoming connection from %s", this, remoteAddr);
        class LocalConnection * conn = new LocalConnection(this);
        localConnections.push_back(conn);
        this->socket->Accept(conn);
    }
}

void UrlHandler::LocalSocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    debug->printf("UrlHandler(%p)::SocketShutdown Local socket: closed", this);
    delete socket;
    this->socket = 0;
    if (stopping) TryStop();
}

void UrlHandler::LocalSocketRecvCanceled(ISocket * const socket, void * buf)
{
}

void UrlHandler::LocalConnectionClosed(class LocalConnection * connection)
{
    localConnections.remove(connection);
    delete connection;
    if (stopping) TryStop();
}

void UrlHandler::HandleUrl(const char * url)
{
    debug->printf("URI: '%s'", url);
    char tmp[4096];
    str::to_str(url, tmp, sizeof(tmp));
    char * scheme = 0;
    char * target = 0;
    char * p = strchr(tmp, ':');
    if (p) {
        *p = 0;
        scheme = tmp;
        target = p + 1;
    }
    if (scheme && target) {
        debug->printf("scheme: '%s', target: '%s'", scheme, target);
        if (!strcmp(scheme, "tel") || !strcmp(scheme, "TEL") || !strcmp(scheme, "Tel") ||
            !strcmp(scheme, "phone") || !strcmp(scheme, "PHONE") || !strcmp(scheme, "Phone") ||
            !strcmp(scheme, "callto") || !strcmp(scheme, "CALLTO") || !strcmp(scheme, "Callto")) {
            p = strchr(target, ';');
            if (p) *p = 0;
            p = target;
            char num[4096];
            char * n = num;
            while (*p && ((unsigned)(n - num) < sizeof(num))) {
                switch (*p) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case '*':
                case '#':
                    *n++ = *p;
                    break;
                case '+':
                    if (n == num) *n++ = *p;
                    break;
                case ',':
                    if (n != num) *n++ = *p;
                    break;
                default:
                    break;
                }
                p++;
            }
            *n = 0;
            debug->printf("Processed target: '%s'", num);
            if (!*num) {
                debug->printf("Empty target.");
            }
            else {
                debug->printf("Starting phone call..");
                bool ret = false;
                if(signalingApp) ret = signalingApp->StartCall(0, num, NULL, START_CALL_VIDEO_MODE_DEFAULT);
                if(launcherApi && !ret) launcherApi->StartCall(0, num, NULL, START_CALL_VIDEO_MODE_DEFAULT);
            }
        }
        else if (!strcmp(scheme, "sip") || !strcmp(scheme, "SIP") || !strcmp(scheme, "Sip") ||
                 !strcmp(scheme, "sips") || !strcmp(scheme, "SIPS") || !strcmp(scheme, "Sips")) {
            debug->printf("Processed target: '%s'", target);
            if (!*target) {
                debug->printf("Empty target.");
            }
            else {
                debug->printf("Starting phone call.");
                bool ret = false;
                if (signalingApp) ret = signalingApp->StartCall(target, 0, NULL, START_CALL_VIDEO_MODE_DEFAULT);
                if (launcherApi && !ret) launcherApi->StartCall(target, 0, NULL, START_CALL_VIDEO_MODE_DEFAULT);
            }
        }
        else if (!strcmp(scheme, "im") || !strcmp(scheme, "IM") || !strcmp(scheme, "Im")) {
            debug->printf("Processed target: '%s'", target);
            if (!*target) {
                debug->printf("Empty target.");
            }
            else {
                debug->printf("Starting chat.");
                launcherApi->StartChat(target);
            }
        }
        else if (!strcmp(scheme, "com.innovaphone.myapps")) {
            debug->printf("Processed target: '%s'", target);
            if (!*target) {
                debug->printf("Empty target.");
            }
            else {
                debug->printf("Open permalink (%s)", target);
                launcherApi->OpenPermalink(target);
            }
        }
        else {
            debug->printf("Unknown URI scheme.");
        }
    }
    else {
        debug->printf("Invalid URI.");
    }
}

bool UrlHandler::IsE164(const char * num)
{
    if (num == nullptr) return false;

    for (unsigned int i = 0; i < strlen(num); i++) {
        if ('0' <= num[i] && num[i] <= '9') continue;
        if (num[i] == '*' || num[i] == '#') continue;
        if (num[i] == '+' && i == 0) continue;
        if (num[i] == ',' && i != 0) continue; // separated dialing digits from dtmf digits
        return false;
    }
    return true;
}

void UrlHandler::Message(char * buffer)
{
    if (!stopping && buffer) {
        json_io json(buffer);
        if (json.decode()) {
            word base = json.get_object(JSON_ID_ROOT, 0);
            const char * mt = json.get_string(base, "mt");
            if (mt) {
                if (!strcmp(mt, "Url")) {
                    const char * url = json.get_string(base, "url");
                    if (url) {
                        HandleUrl(url);
                    }
                }
            }
        }
    }
}

/*---------------------------------------------------------------------------*/
/* class LocalConnection                                                     */
/*---------------------------------------------------------------------------*/

LocalConnection::LocalConnection(UrlHandler * instance)
{
    debug->printf("connection: created");
    this->instance = instance;
    this->recvLen = 0;
    this->jsonLevel = 0;
    this->jsonQuotes = false;
    this->jsonEsc = false;
    this->closing = false;
    this->socket = 0;
}

LocalConnection::~LocalConnection()
{
    debug->printf("connection: deleted");
}

void LocalConnection::Close()
{
    if (!closing) {
        closing = true;
        socket->Shutdown();
    }
}

void LocalConnection::SocketAcceptComplete(ISocket * const socket)
{
    debug->printf("connection: accept complete");
    this->socket = socket;
    this->socket->Recv(&recvBuf[recvLen], sizeof(recvBuf) - recvLen - 1, true);
}

void LocalConnection::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    ASSERT(this->socket, "LocalConnection::SocketShutdown");
    debug->printf("connection: shutdown");
    delete this->socket;
    this->socket = 0;
    instance->LocalConnectionClosed(this);
}

void LocalConnection::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
    unsigned jsonStart;
    unsigned jsonPos;

    ASSERT(this->socket, "LocalConnection::SocketRecvResult");
    if (closing) return;
    jsonStart = 0;
    jsonPos = recvLen;
    recvLen += len;
    while (jsonPos < recvLen) {
        while ((jsonPos < recvLen) && ((recvBuf[jsonPos] != '}') || (jsonLevel > 1) || jsonQuotes)) {
            if (jsonQuotes) {
                if (jsonEsc) {
                    jsonEsc = false;
                }
                else if (recvBuf[jsonPos] == '\\') {
                    jsonEsc = true;
                }
                else if (recvBuf[jsonPos] == '"') {
                    jsonQuotes = false;
                }
            }
            else if (recvBuf[jsonPos] == '"') {
                jsonQuotes = true;
            }
            else if (recvBuf[jsonPos] == '{') {
                jsonLevel++;
            }
            else if (recvBuf[jsonPos] == '}') {
                jsonLevel--;
            }
            jsonPos++;
        }
        if ((jsonPos < recvLen) && (recvBuf[jsonPos] == '}') && (jsonLevel == 1) && !jsonQuotes) {
            jsonPos++;
            recvBuf[jsonPos] = '\0';

            instance->Message(&recvBuf[jsonStart]);

            recvBuf[jsonPos] = '{';
            jsonLevel = 0;
            jsonQuotes = false;
            jsonEsc = false;
            jsonStart = jsonPos;
        }
    }
    if (jsonStart != 0) {
        memmove(recvBuf, &recvBuf[jsonStart], recvLen - jsonStart);
        recvLen -= jsonStart;
    }
    socket->Recv(&recvBuf[recvLen], sizeof(recvBuf) - recvLen - 1, true);
}

void LocalConnection::SocketRecvCanceled(ISocket * const socket, void * buf)
{
}

