/**
 * @file lluriparser.cpp
 * @author Protey
 * @date 2014-10-07
 * @brief Implementation of the LLUriParser class.
 *
 * $LicenseInfo:firstyear=2014&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2014, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "lluriparser.h"

#if LL_DARWIN
#include <signal.h>
#include <setjmp.h>
#endif

LLUriParser::LLUriParser(const std::string& u) : mTmpScheme(false), mNormalizedTmp(false), mRes(0)
{
    if (u.find("://") == std::string::npos)
    {
        mNormalizedUri = "http://";
        mTmpScheme = true;
    }

    mNormalizedUri += u.c_str();

    mRes = parse();
}

LLUriParser::~LLUriParser()
{
    uriFreeUriMembersA(&mUri);
}

S32 LLUriParser::parse()
{
    mRes = uriParseSingleUriA(&mUri, mNormalizedUri.c_str(), NULL);
    return mRes;
}

const char * LLUriParser::scheme() const
{
    return mScheme.c_str();
}

void LLUriParser::sheme(const std::string& s)
{
    mTmpScheme = !s.size();
    mScheme = s;
}

const char * LLUriParser::port() const
{
    return mPort.c_str();
}

void LLUriParser::port(const std::string& s)
{
    mPort = s;
}

const char * LLUriParser::host() const
{
    return mHost.c_str();
}

void LLUriParser::host(const std::string& s)
{
    mHost = s;
}

const char * LLUriParser::path() const
{
    return mPath.c_str();
}

void LLUriParser::path(const std::string& s)
{
    mPath = s;
}

const char * LLUriParser::query() const
{
    return mQuery.c_str();
}

void LLUriParser::query(const std::string& s)
{
    mQuery = s;
}

const char * LLUriParser::fragment() const
{
    return mFragment.c_str();
}

void LLUriParser::fragment(const std::string& s)
{
    mFragment = s;
}

void LLUriParser::textRangeToString(UriTextRangeA& textRange, std::string& str)
{
    if (textRange.first != NULL && textRange.afterLast != NULL && textRange.first < textRange.afterLast)
    {
        const ptrdiff_t len = textRange.afterLast - textRange.first;
        str.assign(textRange.first, static_cast<std::string::size_type>(len));
    }
    else
    {
        str = LLStringUtil::null;
    }
}

void LLUriParser::extractParts()
{
    if (mTmpScheme || mNormalizedTmp)
    {
        mScheme.clear();
    }
    else
    {
        textRangeToString(mUri.scheme, mScheme);
    }

    textRangeToString(mUri.hostText, mHost);
    textRangeToString(mUri.portText, mPort);
    textRangeToString(mUri.query, mQuery);
    textRangeToString(mUri.fragment, mFragment);

    UriPathSegmentA * pathHead = mUri.pathHead;
    while (pathHead)
    {
        std::string partOfPath;
        textRangeToString(pathHead->text, partOfPath);

        mPath += '/';
        mPath += partOfPath;

        pathHead = pathHead->next;
    }
}

#if LL_DARWIN
typedef void(*sighandler_t)(int);
jmp_buf return_to_normalize;
static int sLastSignal = 0;
void uri_signal_handler(int signal)
{
    sLastSignal = signal;
    // Apparently signal handler throwing an exception doesn't work.
    // This is ugly and unsafe due to not unwinding content of uriparser library,
    // but unless we have a way to catch this as NSexception, jump appears to be the only option.
    longjmp(return_to_normalize, 1 /*setjmp will return this value*/);
}
#endif

S32 LLUriParser::normalize()
{
    mNormalizedTmp = mTmpScheme;
    if (!mRes)
    {
#if LL_DARWIN
        sighandler_t last_sigill_handler, last_sigbus_handler;
        last_sigill_handler = signal(SIGILL, &uri_signal_handler);      // illegal instruction
        last_sigbus_handler = signal(SIGBUS, &uri_signal_handler);

        if (setjmp(return_to_normalize))
        {
            // Issue: external library crashed via signal
            // If you encountered this, please try to figure out what's wrong:
            // 1. Verify that library's input is 'sane'
            // 2. Check if we have an NSexception to work with (unlikely)
            // 3. See if passing same string causes exception to repeat
            //
            // Crash happens at uriNormalizeSyntaxExA
            // Warning!!! This does not properly unwind stack,
            // if this can be handled by NSexception, it needs to be remade
            llassert(0);

            LL_WARNS() << "Uriparser crashed with " << sLastSignal << " , while processing: " << mNormalizedUri << LL_ENDL;
            signal(SIGILL, last_sigill_handler);
            signal(SIGBUS, last_sigbus_handler);
            return 1;
        }
#endif

        mRes = uriNormalizeSyntaxExA(&mUri, URI_NORMALIZE_SCHEME | URI_NORMALIZE_HOST);

#if LL_DARWIN
        signal(SIGILL, last_sigill_handler);
        signal(SIGBUS, last_sigbus_handler);
#endif

        if (!mRes)
        {
            S32 chars_required;
            mRes = uriToStringCharsRequiredA(&mUri, &chars_required);

            if (!mRes)
            {
                chars_required++;
                std::vector<char> label_buf(chars_required);
                mRes = uriToStringA(&label_buf[0], &mUri, chars_required, NULL);

                if (!mRes)
                {
                    mNormalizedUri = &label_buf[mTmpScheme ? 7 : 0];
                    mTmpScheme = false;
                }
            }
        }
    }

    if(mTmpScheme && mNormalizedUri.size() > 7)
    {
        mNormalizedUri = mNormalizedUri.substr(7);
        mTmpScheme = false;
    }

    return mRes;
}

void LLUriParser::glue(std::string& uri) const
{
    std::string first_part;
    glueFirst(first_part);

    std::string second_part;
    glueSecond(second_part);

    uri = first_part + second_part;
}

void LLUriParser::glueFirst(std::string& uri, bool use_scheme) const
{
    if (use_scheme && mScheme.size())
    {
        uri = mScheme;
        uri += "://";
    }
    else
    {
        uri.clear();
    }

    uri += mHost;
}

void LLUriParser::glueSecond(std::string& uri) const
{
    if (mPort.size())
    {
        uri = ':';
        uri += mPort;
    }
    else
    {
        uri.clear();
    }

    uri += mPath;

    if (mQuery.size())
    {
        uri += '?';
        uri += mQuery;
    }

    if (mFragment.size())
    {
        uri += '#';
        uri += mFragment;
    }
}

bool LLUriParser::test() const
{
    std::string uri;
    glue(uri);

    return uri == mNormalizedUri;
}

const char * LLUriParser::normalizedUri() const
{
    return mNormalizedUri.c_str();
}