/*
 * Copyright (C) 2011 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 "NewXMLDocumentParser.h"

#include "DocumentFragment.h"
#include "Element.h"
#include "ScriptElement.h"
#include "ScriptSourceCode.h"
#include "SegmentedString.h"
#include "XMLTreeBuilder.h"
#include "XMLTreeViewer.h"

namespace WebCore {

NewXMLDocumentParser::NewXMLDocumentParser(Document* document)
    : ScriptableDocumentParser(document)
    , m_tokenizer(XMLTokenizer::create())
    , m_parserPaused(false)
    , m_finishWasCalled(false)
    , m_pendingScript(0)
    , m_scriptElement(0)
    , m_treeBuilder(XMLTreeBuilder::create(this, document))
{
}

NewXMLDocumentParser::NewXMLDocumentParser(DocumentFragment* fragment, Element* parent, FragmentScriptingPermission)
    : ScriptableDocumentParser(fragment->document())
    , m_tokenizer(XMLTokenizer::create())
    , m_parserPaused(false)
    , m_finishWasCalled(false)
    , m_pendingScript(0)
    , m_scriptElement(0)
    , m_treeBuilder(XMLTreeBuilder::create(this, fragment, parent))
{
}

bool NewXMLDocumentParser::parseDocumentFragment(const String& chunk, DocumentFragment* fragment, Element* contextElement, FragmentScriptingPermission scriptingPermission)
{
    if (!chunk.length())
        return true;

    RefPtr<NewXMLDocumentParser> parser = NewXMLDocumentParser::create(fragment, contextElement, scriptingPermission);
    parser->append(SegmentedString(chunk));
    // Do not call finish(). Current finish() implementation touches the main Document/loader
    // and can cause crashes in the fragment case.
    parser->detach(); // Allows ~DocumentParser to assert it was detached before destruction.

    // FIXME: return false if not well-formed
    return true;
}

NewXMLDocumentParser::~NewXMLDocumentParser()
{
}

void NewXMLDocumentParser::resumeParsing()
{
    m_parserPaused = false;
    append(m_input);
}

void NewXMLDocumentParser::processScript(ScriptElement* scriptElement)
{
    if (scriptElement->prepareScript(TextPosition(), ScriptElement::AllowLegacyTypeInTypeAttribute)) {
        if (scriptElement->readyToBeParserExecuted())
            scriptElement->executeScript(ScriptSourceCode(scriptElement->scriptContent(), document()->url(), TextPosition()));
        else if (scriptElement->willBeParserExecuted()) {
            m_pendingScript = scriptElement->cachedScript();
            m_scriptElement = scriptElement->element();
            m_pendingScript->addClient(this);

            // m_pendingScript will be 0 if script was already loaded and addClient() executed it.
            if (m_pendingScript)
                pauseParsing();
        } else
            m_scriptElement = 0;
    }
}

TextPosition NewXMLDocumentParser::textPosition() const
{
    return TextPosition::minimumPosition();
}

OrdinalNumber NewXMLDocumentParser::lineNumber() const
{
    return OrdinalNumber::first();
}

void NewXMLDocumentParser::insert(const SegmentedString&)
{
    ASSERT_NOT_REACHED();
}

void NewXMLDocumentParser::append(const SegmentedString& string)
{
    m_input = string;
    while (!m_input.isEmpty() && isParsing() && !m_parserPaused) {
        if (!m_tokenizer->nextToken(m_input, m_token))
            continue;

#ifndef NDEBUG
        m_token.print();
#endif

        AtomicXMLToken token(m_token);
        m_treeBuilder->processToken(token);

        if (m_token.type() == XMLTokenTypes::EndOfFile)
            break;

        m_token.clear();
        ASSERT(m_token.isUninitialized());
    }
}

void NewXMLDocumentParser::finish()
{
    ASSERT(!m_finishWasCalled);

    if (m_parserPaused)
        return;

    m_treeBuilder->finish();

    m_finishWasCalled = true;
    if (isParsing()) {
#if ENABLE(XSLT)
        XMLTreeViewer xmlTreeViewer(document());
        if (xmlTreeViewer.hasNoStyleInformation())
            xmlTreeViewer.transformDocumentToTreeView();
#endif // ENABLE(XSLT)

        prepareToStopParsing();
    }
    document()->setReadyState(Document::Interactive);
    document()->finishedParsing();
}

bool NewXMLDocumentParser::hasInsertionPoint()
{
    return false;
}

bool NewXMLDocumentParser::finishWasCalled()
{
    return m_finishWasCalled;
}

bool NewXMLDocumentParser::isWaitingForScripts() const
{
    return false;
}

bool NewXMLDocumentParser::isExecutingScript() const
{
    return false;
}

void NewXMLDocumentParser::executeScriptsWaitingForStylesheets()
{
}

void NewXMLDocumentParser::notifyFinished(CachedResource* unusedResource)
{
    ASSERT_UNUSED(unusedResource, unusedResource == m_pendingScript);
    ASSERT(m_pendingScript->accessCount() > 0);

    ScriptSourceCode sourceCode(m_pendingScript.get());
    bool errorOccurred = m_pendingScript->errorOccurred();
    bool wasCanceled = m_pendingScript->wasCanceled();

    m_pendingScript->removeClient(this);
    m_pendingScript = 0;

    RefPtr<Element> element = m_scriptElement;
    ScriptElement* scriptElement = toScriptElement(m_scriptElement.get());
    m_scriptElement = 0;

    ASSERT(scriptElement);

    // JavaScript can detach this parser, make sure it's kept alive even if detached.
    RefPtr<NewXMLDocumentParser> protect(this);

    if (errorOccurred)
        scriptElement->dispatchErrorEvent();
    else if (!wasCanceled) {
        scriptElement->executeScript(sourceCode);
        scriptElement->dispatchLoadEvent();
    }

    if (!isDetached() && m_parserPaused)
        resumeParsing();
}

}
