/*
 * 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.
 */

#pragma once

#if ENABLE(WEB_REPLAY)

#include "EventLoopInputDispatcher.h"
#include <wtf/Noncopyable.h>

// Determinism assertions are guarded by this macro. When a user-facing error reporting and
// recovery mechanism is implemented, this guard can be removed. <https://webkit.org/b/131279>
#define ENABLE_AGGRESSIVE_DETERMINISM_CHECKS 0

namespace JSC {
class InputCursor;
}

namespace WebCore {

class DOMWindow;
class Document;
class DocumentLoader;
class Element;
class Event;
class EventLoopInputBase;
class Frame;
class Node;
class Page;
class ReplaySession;
class ReplaySessionSegment;

// Each state may transition to the state immediately above or below it.
// SessionState transitions are only allowed when SegmentState is Unloaded.
enum class SessionState {
    Capturing,
    // Neither capturing or replaying. m_currentPosition is not valid in this state.
    Inactive,
    Replaying,
};

// Each state may transition to the state immediately above or below it.
enum class SegmentState {
    // Inputs can be appended into an unassociated session segment.
    // We can stop capturing, which reverts to the Unloaded state.
    Appending,
    // No session segment is loaded.
    // We can start capturing, or load a segment (and then replay it).
    Unloaded,
    // A session segment is loaded.
    // We can unload the segment, or begin playback from m_currentPosition.
    Loaded,
    // The controller is actively dispatching event loop inputs.
    // We can pause or cancel playback, which reverts to the Loaded state.
    Dispatching,
};

struct ReplayPosition {
    ReplayPosition(unsigned segmentOffset, unsigned inputOffset)
        : segmentOffset(segmentOffset)
        , inputOffset(inputOffset)
    {
    }

    // By convention, this position represents the end of the last segment of the session.
    ReplayPosition()
        : segmentOffset(0)
        , inputOffset(0)
    {
    }

    bool operator<(const ReplayPosition& other)
    {
        return segmentOffset <= other.segmentOffset && inputOffset < other.inputOffset;
    }

    bool operator==(const ReplayPosition& other)
    {
        return segmentOffset == other.segmentOffset && inputOffset == other.inputOffset;
    }

    unsigned segmentOffset;
    unsigned inputOffset;
};

class ReplayController final : public EventLoopInputDispatcherClient {
    WTF_MAKE_FAST_ALLOCATED;
    WTF_MAKE_NONCOPYABLE(ReplayController);
public:
    ReplayController(Page&);

    void startCapturing();
    void stopCapturing();

    // Start or resume playback with default speed and target replay position.
    void startPlayback();
    void pausePlayback();
    void cancelPlayback();

    void replayToPosition(const ReplayPosition&, DispatchSpeed = DispatchSpeed::FastForward);
    void replayToCompletion(DispatchSpeed speed = DispatchSpeed::FastForward)
    {
        replayToPosition(ReplayPosition(), speed);
    }

    void switchSession(RefPtr<ReplaySession>&&);

    // InspectorReplayAgent notifications.
    void frameNavigated(Frame&);
    void frameDetached(Frame&);
    void willDispatchEvent(const Event&, Frame*);

    Page& page() const { return m_page; }

    SessionState sessionState() const { return m_sessionState; }
    SegmentState segmentState() const { return m_segmentState; }

    RefPtr<ReplaySession> loadedSession() const;
    RefPtr<ReplaySessionSegment> loadedSegment() const;

    JSC::InputCursor& activeInputCursor();
    ReplayPosition currentPosition() const { return m_currentPosition; }

private:
    // EventLoopInputDispatcherClient API
    void willDispatchInput(const EventLoopInputBase&) override;
    void didDispatchInput(const EventLoopInputBase&) override;
    void didDispatchFinalInput() override;

    void createSegment();
    void completeSegment();

    void loadSegmentAtIndex(size_t);
    void unloadSegment(bool suppressNotifications = false);

    EventLoopInputDispatcher& dispatcher() const;

    void setSessionState(SessionState);
    void setSegmentState(SegmentState);
    void setForceDeterministicSettings(bool);

    struct SavedSettings {
        bool usesPageCache;

        SavedSettings()
            : usesPageCache(false)
        { }
    };

    Page& m_page;

    RefPtr<ReplaySessionSegment> m_loadedSegment;
    RefPtr<ReplaySession> m_loadedSession;
    Ref<JSC::InputCursor> m_emptyCursor;
    // The active cursor is set to nullptr when invalid.
    RefPtr<JSC::InputCursor> m_activeCursor;

    // This position is valid when SessionState == Replaying.
    ReplayPosition m_targetPosition;
    // This position is valid when SessionState != Inactive.
    ReplayPosition m_currentPosition;
    SegmentState m_segmentState;
    // This tracks state across multiple segments. When navigating the main frame,
    // there is a small interval during segment switching when no segment is loaded.
    SessionState m_sessionState;

    DispatchSpeed m_dispatchSpeed;
    SavedSettings m_savedSettings;
};

} // namespace WebCore

#endif // ENABLE(WEB_REPLAY)
