/*
 * Copyright (C) 2010 Google, Inc. All Rights Reserved.
 * Copyright (C) 2012 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "ParsedURL.h"

#if USE(WTFURL)

#include <wtf/DataLog.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringImpl.h>
#include <wtf/url/src/RawURLBuffer.h>
#include <wtf/url/src/URLComponent.h>
#include <wtf/url/src/URLUtil.h>

namespace WTF {

ParsedURL::ParsedURL(const String& urlString, ParsedURLStringTag)
{
    unsigned urlStringLength = urlString.length();
    if (!urlStringLength)
        return; // FIXME: we should ASSERT on this, but people use KURL incorrectly with ParsedURLStringTag :(.

    RawURLBuffer<char> outputBuffer;
    String base;
    const CString& baseStr = base.utf8();
    bool isValid = false;
    URLSegments baseSegments;

    // FIXME: we should take shortcuts here! We do not have to resolve the relative part.
    if (urlString.is8Bit())
        isValid = URLUtilities::resolveRelative(baseStr.data(), baseSegments,
                                                reinterpret_cast<const char*>(urlString.characters8()), urlStringLength,
                                                /* charsetConverter */ 0,
                                                outputBuffer, &m_segments);
    else
        isValid = URLUtilities::resolveRelative(baseStr.data(), baseSegments,
                                                urlString.characters16(), urlStringLength,
                                                /* charsetConverter */ 0,
                                                outputBuffer, &m_segments);

    // FIXME: we should ASSERT on isValid, but people use KURL incorrectly with ParsedURLStringTag :(.
    if (isValid)
        m_spec = URLString(String(outputBuffer.data(), outputBuffer.length()));
}

ParsedURL::ParsedURL(const String& urlString, URLQueryCharsetConverter* queryCharsetConverter)
{
    unsigned urlStringLength = urlString.length();
    if (!urlStringLength)
        return;

    RawURLBuffer<char> outputBuffer;
    String base;
    const CString& baseStr = base.utf8();
    bool isValid = false;
    URLSegments baseSegments;

    // FIXME: we should take shortcuts here! We do not have to resolve the relative part.
    if (urlString.is8Bit())
        isValid = URLUtilities::resolveRelative(baseStr.data(), baseSegments,
                                                reinterpret_cast<const char*>(urlString.characters8()), urlStringLength,
                                                queryCharsetConverter,
                                                outputBuffer, &m_segments);
    else
        isValid = URLUtilities::resolveRelative(baseStr.data(), baseSegments,
                                                urlString.characters16(), urlStringLength,
                                                queryCharsetConverter,
                                                outputBuffer, &m_segments);

    if (isValid)
        m_spec = URLString(String(outputBuffer.data(), outputBuffer.length()));
}

ParsedURL::ParsedURL(const ParsedURL& base, const String& relative, URLQueryCharsetConverter* queryCharsetConverter)
{
    if (!base.isValid())
        return;

    unsigned relativeLength = relative.length();
    if (!relativeLength) {
        *this = base.withoutFragment();
        return;
    }

    RawURLBuffer<char> outputBuffer;
    const CString& baseStr = base.m_spec.m_string.utf8();
    bool isValid = false;

    if (relative.is8Bit())
        isValid = URLUtilities::resolveRelative(baseStr.data(), base.m_segments,
                                                reinterpret_cast<const char*>(relative.characters8()), relativeLength,
                                                queryCharsetConverter,
                                                outputBuffer, &m_segments);
    else
        isValid = URLUtilities::resolveRelative(baseStr.data(), base.m_segments,
                                                relative.characters16(), relativeLength,
                                                queryCharsetConverter,
                                                outputBuffer, &m_segments);

    if (isValid)
        m_spec = URLString(String(outputBuffer.data(), outputBuffer.length()));
}

ParsedURL ParsedURL::isolatedCopy() const
{
    ParsedURL copy;
    copy.m_segments = this->m_segments;
    copy.m_spec = URLString(this->m_spec.string().isolatedCopy());
    return copy;
}

String ParsedURL::scheme() const
{
    return segment(m_segments.scheme);
}

bool ParsedURL::hasStandardScheme() const
{
    ASSERT(m_segments.scheme.isValid());
    const String& urlStringSpec = m_spec.m_string;
    if (urlStringSpec.is8Bit())
        return URLUtilities::isStandard(urlStringSpec.characters8(), m_segments.scheme);
    return URLUtilities::isStandard(urlStringSpec.characters16(), m_segments.scheme);
}

String ParsedURL::username() const
{
    return segment(m_segments.username);
}

String ParsedURL::password() const
{
    return segment(m_segments.password);
}

String ParsedURL::host() const
{
    return segment(m_segments.host);
}

bool ParsedURL::hasPort() const
{
    return m_segments.port.isNonEmpty();
}

String ParsedURL::port() const
{
    return segment(m_segments.port);
}

template<typename CharacterType>
static inline String generateNewSpecWithPort(const String& spec, unsigned newSpecLength, unsigned portDelimiterPosition, const LChar* portString, unsigned portStringLength, unsigned postPortPositionInSource)
{
    ASSERT(newSpecLength == portDelimiterPosition + 1 + portStringLength + (spec.length() - postPortPositionInSource));

    CharacterType* buffer;
    String newSpec = StringImpl::createUninitialized(newSpecLength, buffer);

    // Copy everything prior to the port posisiton.
    ASSERT(buffer + portDelimiterPosition < buffer + newSpecLength);
    StringImpl::copyChars(buffer, spec.getCharacters<CharacterType>(), portDelimiterPosition);

    // Add the new port from the position.
    buffer[portDelimiterPosition] = ':';
    unsigned portPosition = portDelimiterPosition + 1;
    ASSERT(buffer + portPosition + portStringLength <= buffer + newSpecLength);
    StringImpl::copyChars(buffer + portPosition, portString, portStringLength);

    // Copy the character post-port from the source.
    unsigned remainingComponentsPositionInDestination = portPosition + portStringLength;
    ASSERT(buffer + remainingComponentsPositionInDestination + (spec.length() - postPortPositionInSource) == buffer + newSpecLength);
    StringImpl::copyChars(buffer + remainingComponentsPositionInDestination, &(spec.getCharacters<CharacterType>()[postPortPositionInSource]), spec.length() - postPortPositionInSource);

    return newSpec;
}

static inline void replacePortWithString(String& spec, URLSegments& segments, const LChar* portString, unsigned portStringLength)
{
    // Compute the new spec length.
    int lengthDifference;
    const URLComponent oldPortComponent = segments.port;
    if (oldPortComponent.isValid())
        lengthDifference = portStringLength - oldPortComponent.length();
    else
        lengthDifference = 1 + portStringLength;
    unsigned newLength = spec.length() + lengthDifference;

    // Find the substring positions for the generator template.
    int portDelimiterPosition = segments.charactersBefore(URLSegments::Port, URLSegments::DelimiterIncluded);
    ASSERT(portDelimiterPosition > 0);

    unsigned postPortPositionInSource;
    if (oldPortComponent.isValid())
        postPortPositionInSource = oldPortComponent.end();
    else
        postPortPositionInSource = portDelimiterPosition;

    // Create the new spec with portString.
    if (spec.is8Bit())
        spec = generateNewSpecWithPort<LChar>(spec, newLength, static_cast<unsigned>(portDelimiterPosition), portString, portStringLength, postPortPositionInSource);
    else
        spec = generateNewSpecWithPort<UChar>(spec, newLength, static_cast<unsigned>(portDelimiterPosition), portString, portStringLength, postPortPositionInSource);

    // Update the URL components.
    unsigned portPosition = portDelimiterPosition + 1;
    segments.port.setBegin(portPosition);
    segments.port.setLength(portStringLength);
    segments.moveFromComponentBy(URLSegments::Path, lengthDifference);
}


void ParsedURL::replacePort(unsigned short newPort)
{
    ASSERT(hasStandardScheme());

    // Generate a char* string for the port number.
    LChar buf[5];
    LChar* end = buf + WTF_ARRAY_LENGTH(buf);
    LChar* p = end;
    do {
        *--p = static_cast<LChar>((newPort % 10) + '0');
        newPort /= 10;
    } while (newPort);
    const unsigned portStringLength = end - p;

    replacePortWithString(m_spec.m_string, m_segments, p, portStringLength);
}

void ParsedURL::removePort()
{
    if (!hasPort())
        return;

    // 1) Remove the port from the spec, including the delimiter.
    String newSpec;
    int beginning = m_segments.port.begin() - 1;
    unsigned length = m_segments.port.length() + 1;

    String newSpecString = m_spec.string();
    newSpecString.remove(beginning, length);
    m_spec = URLString(newSpecString);

    // 2) Update the components positions.
    m_segments.port.reset();
    m_segments.moveFromComponentBy(URLSegments::Path, -length);
}

String ParsedURL::path() const
{
    return segment(m_segments.path);
}

String ParsedURL::query() const
{
    return segment(m_segments.query);
}

bool ParsedURL::hasFragment() const
{
    return m_segments.fragment.isValid();
}

String ParsedURL::fragment() const
{
    return segment(m_segments.fragment);
}

ParsedURL ParsedURL::withoutFragment() const
{
    if (!hasFragment())
        return *this;

    ParsedURL newURL;

    int charactersBeforeFragemnt = m_segments.charactersBefore(URLSegments::Fragment, URLSegments::DelimiterExcluded);
    newURL.m_spec = URLString(m_spec.string().substringSharingImpl(0, charactersBeforeFragemnt));

    newURL.m_segments = m_segments;
    newURL.m_segments.fragment = URLComponent();
    return newURL;
}

String ParsedURL::baseAsString() const
{
    // FIXME: Add WTFURL Implementation.
    return String();
}

String ParsedURL::segment(const URLComponent& component) const
{
    ASSERT(isValid());

    if (!component.isValid())
        return String();

    String segment = m_spec.string().substring(component.begin(), component.length());

    // FIXME: GoogleURL create empty segments. This happen for the fragment for the test fast/url/segments.html
    // ASSERT_WITH_MESSAGE(!segment.isEmpty(), "A valid URL component should not be empty.");
    return segment;
}

#ifndef NDEBUG

#define SHOW_COMPONENT(parsedURL, componentName) \
    if (!parsedURL->componentName().isNull()) { \
        dataLog("    " #componentName " = "); \
        parsedURL->componentName().show(); \
    }

void ParsedURL::print() const
{
    if (!isValid()) {
        dataLog("Invalid ParsedURL.\n");
        return;
    }

    dataLog("Valid ParsedURL with:\n");
    dataLog("    m_spec = ");
    m_spec.print();

    SHOW_COMPONENT(this, scheme);
    SHOW_COMPONENT(this, username);
    SHOW_COMPONENT(this, password);
    SHOW_COMPONENT(this, host);
    SHOW_COMPONENT(this, port);
    SHOW_COMPONENT(this, path);
    SHOW_COMPONENT(this, query);
    SHOW_COMPONENT(this, fragment);
}
#endif

}

#endif // USE(WTFURL)
