/*
 * Copyright (C) 2011-2013 University of Washington. All rights reserved.
 * Copyright (C) 2014 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT
 * HOLDER 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 "EventLoopInputDispatcher.h"

#if ENABLE(WEB_REPLAY)

#include "Page.h"
#include "ReplayingInputCursor.h"
#include "WebReplayInputs.h"
#include <wtf/TemporaryChange.h>

#if !LOG_DISABLED
#include "Logging.h"
#include "SerializationMethods.h"
#include <replay/EncodedValue.h>
#include <wtf/text/CString.h>
#endif

namespace WebCore {

EventLoopInputDispatcher::EventLoopInputDispatcher(Page& page, ReplayingInputCursor& cursor, EventLoopInputDispatcherClient* client)
    : m_page(page)
    , m_client(client)
    , m_cursor(cursor)
    , m_timer(*this, &EventLoopInputDispatcher::timerFired)
    , m_speed(DispatchSpeed::FastForward)
{
    m_currentWork.input = nullptr;
    m_currentWork.timestamp = 0.0;
}

void EventLoopInputDispatcher::run()
{
    ASSERT(!m_running);
    m_running = true;

    LOG(WebReplay, "%-20s Starting dispatch of event loop inputs for page: %p\n", "ReplayEvents", &m_page);
    dispatchInputSoon();
}

void EventLoopInputDispatcher::pause()
{
    ASSERT(!m_dispatching);
    ASSERT(m_running);
    m_running = false;

    LOG(WebReplay, "%-20s Pausing dispatch of event loop inputs for page: %p\n", "ReplayEvents", &m_page);
    if (m_timer.isActive())
        m_timer.stop();
}

void EventLoopInputDispatcher::timerFired()
{
    dispatchInput();
}

void EventLoopInputDispatcher::dispatchInputSoon()
{
    ASSERT(m_running);

    // We may already have an input if replay was paused just before dispatching.
    if (!m_currentWork.input)
        m_currentWork = m_cursor.loadEventLoopInput();

    if (m_timer.isActive())
        m_timer.stop();

    double waitInterval = 0;

    if (m_speed == DispatchSpeed::RealTime) {
        // The goal is to reproduce the dispatch delay between inputs as it was
        // was observed during the recording. So, we need to compute how much time
        // to wait such that the elapsed time plus the wait time will equal the
        // observed delay between the previous and current input.

        if (!m_previousInputTimestamp)
            m_previousInputTimestamp = m_currentWork.timestamp;

        double targetInterval = m_currentWork.timestamp - m_previousInputTimestamp;
        double elapsed = monotonicallyIncreasingTime() - m_previousDispatchStartTime;
        waitInterval = targetInterval - elapsed;
    }

    // A negative wait time means that dispatch took longer on replay than on
    // capture. In this case, proceed without waiting at all.
    if (waitInterval < 0)
        waitInterval = 0;

    if (waitInterval > 1000.0) {
        LOG_ERROR("%-20s Tried to wait for over 1000 seconds before dispatching next event loop input; this is probably a bug.", "ReplayEvents");
        waitInterval = 0;
    }

    LOG(WebReplay, "%-20s (WAIT: %.3f ms)", "ReplayEvents", waitInterval * 1000.0);
    m_timer.startOneShot(waitInterval);
}

void EventLoopInputDispatcher::dispatchInput()
{
    ASSERT(m_currentWork.input);
    ASSERT(!m_dispatching);

    if (m_speed == DispatchSpeed::RealTime) {
        m_previousDispatchStartTime = monotonicallyIncreasingTime();
        m_previousInputTimestamp = m_currentWork.timestamp;
    }

#if !LOG_DISABLED
    EncodedValue encodedInput = EncodingTraits<NondeterministicInputBase>::encodeValue(*m_currentWork.input);
    String jsonString = encodedInput.asObject()->toJSONString();

    LOG(WebReplay, "%-20s ----------------------------------------------", "ReplayEvents");
    LOG(WebReplay, "%-20s >DISPATCH: %s %s\n", "ReplayEvents", m_currentWork.input->type().utf8().data(), jsonString.utf8().data());
#endif

    m_client->willDispatchInput(*m_currentWork.input);
    // Client could stop replay in the previous callback, so check again.
    if (!m_running)
        return;

    {
        TemporaryChange<bool> change(m_dispatching, true);
        m_currentWork.input->dispatch(m_page.replayController());
    }

    EventLoopInputBase* dispatchedInput = m_currentWork.input;
    m_currentWork.input = nullptr;

    // Notify clients that the event was dispatched.
    m_client->didDispatchInput(*dispatchedInput);
    if (dispatchedInput->type() == InputTraits<EndSegmentSentinel>::type()) {
        m_running = false;
        m_dispatching = false;
        m_client->didDispatchFinalInput();
        return;
    }

    // Clients could stop replay during event dispatch, or from any callback above.
    if (!m_running)
        return;

    dispatchInputSoon();
}

} // namespace WebCore

#endif // ENABLE(WEB_REPLAY)
