/*
 * 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 "StringPrintStream.h"

#include <stdarg.h>
#include <stdio.h>
#include <wtf/FastMalloc.h>
#include <wtf/StringExtras.h>

namespace WTF {

StringPrintStream::StringPrintStream()
    : m_buffer(m_inlineBuffer)
    , m_next(0)
    , m_size(sizeof(m_inlineBuffer))
{
    m_buffer[0] = 0; // Make sure that we always have a null terminator.
}

StringPrintStream::~StringPrintStream()
{
    if (m_buffer == m_inlineBuffer)
        return;
    fastFree(m_buffer);
}

void StringPrintStream::vprintf(const char* format, va_list argList)
{
    ASSERT_WITH_SECURITY_IMPLICATION(m_next < m_size);
    ASSERT(!m_buffer[m_next]);

    va_list firstPassArgList;
    va_copy(firstPassArgList, argList);

    int numberOfBytesNotIncludingTerminatorThatWouldHaveBeenWritten =
        vsnprintf(m_buffer + m_next, m_size - m_next, format, firstPassArgList);

    va_end(firstPassArgList);

    int numberOfBytesThatWouldHaveBeenWritten =
        numberOfBytesNotIncludingTerminatorThatWouldHaveBeenWritten + 1;

    if (m_next + numberOfBytesThatWouldHaveBeenWritten <= m_size) {
        m_next += numberOfBytesNotIncludingTerminatorThatWouldHaveBeenWritten;
        return; // This means that vsnprintf() succeeded.
    }

    increaseSize(m_next + numberOfBytesThatWouldHaveBeenWritten);

    int numberOfBytesNotIncludingTerminatorThatWereWritten =
        vsnprintf(m_buffer + m_next, m_size - m_next, format, argList);

    int numberOfBytesThatWereWritten = numberOfBytesNotIncludingTerminatorThatWereWritten + 1;

    ASSERT_UNUSED(numberOfBytesThatWereWritten, m_next + numberOfBytesThatWereWritten <= m_size);

    m_next += numberOfBytesNotIncludingTerminatorThatWereWritten;

    ASSERT_WITH_SECURITY_IMPLICATION(m_next < m_size);
    ASSERT(!m_buffer[m_next]);
}

CString StringPrintStream::toCString()
{
    ASSERT(m_next == strlen(m_buffer));
    return CString(m_buffer, m_next);
}

void StringPrintStream::reset()
{
    m_next = 0;
    m_buffer[0] = 0;
}

String StringPrintStream::toString()
{
    ASSERT(m_next == strlen(m_buffer));
    return String::fromUTF8(m_buffer, m_next);
}

String StringPrintStream::toStringWithLatin1Fallback()
{
    ASSERT(m_next == strlen(m_buffer));
    return String::fromUTF8WithLatin1Fallback(m_buffer, m_next);
}

void StringPrintStream::increaseSize(size_t newSize)
{
    ASSERT_WITH_SECURITY_IMPLICATION(newSize > m_size);
    ASSERT(newSize > sizeof(m_inlineBuffer));

    // Use exponential resizing to reduce thrashing.
    m_size = newSize << 1;

    // Use fastMalloc instead of fastRealloc because we know that for the sizes we're using,
    // fastRealloc will just do malloc+free anyway. Also, this simplifies the code since
    // we can't realloc the inline buffer.
    char* newBuffer = static_cast<char*>(fastMalloc(m_size));
    memcpy(newBuffer, m_buffer, m_next + 1);
    if (m_buffer != m_inlineBuffer)
        fastFree(m_buffer);
    m_buffer = newBuffer;
}

} // namespace WTF

