// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "config.h"
#include "modules/screen_orientation/ScreenOrientationController.h"

#include "core/events/Event.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/page/Page.h"
#include "modules/screen_orientation/ScreenOrientation.h"
#include "modules/screen_orientation/ScreenOrientationDispatcher.h"
#include "platform/LayoutTestSupport.h"
#include "platform/PlatformScreen.h"
#include "public/platform/WebScreenOrientationClient.h"

namespace blink {

ScreenOrientationController::~ScreenOrientationController()
{
}

void ScreenOrientationController::persistentHostHasBeenDestroyed()
{
    // Unregister lifecycle observation once page is being torn down.
    observeContext(0);
}

void ScreenOrientationController::provideTo(LocalFrame& frame, WebScreenOrientationClient* client)
{
    ASSERT(RuntimeEnabledFeatures::screenOrientationEnabled());

    ScreenOrientationController* controller = new ScreenOrientationController(frame, client);
    WillBeHeapSupplement<LocalFrame>::provideTo(frame, supplementName(), adoptPtrWillBeNoop(controller));
}

ScreenOrientationController* ScreenOrientationController::from(LocalFrame& frame)
{
    return static_cast<ScreenOrientationController*>(WillBeHeapSupplement<LocalFrame>::from(frame, supplementName()));
}

ScreenOrientationController::ScreenOrientationController(LocalFrame& frame, WebScreenOrientationClient* client)
    : PlatformEventController(frame.page())
    , m_client(client)
    , m_frame(frame)
    , m_dispatchEventTimer(this, &ScreenOrientationController::dispatchEventTimerFired)
{
}

const char* ScreenOrientationController::supplementName()
{
    return "ScreenOrientationController";
}

// Compute the screen orientation using the orientation angle and the screen width / height.
WebScreenOrientationType ScreenOrientationController::computeOrientation(FrameView* view)
{
    // Bypass orientation detection in layout tests to get consistent results.
    // FIXME: The screen dimension should be fixed when running the layout tests to avoid such
    // issues.
    if (LayoutTestSupport::isRunningLayoutTest())
        return WebScreenOrientationPortraitPrimary;

    FloatRect rect = screenRect(view);
    uint16_t rotation = screenOrientationAngle(view);
    bool isTallDisplay = rotation % 180 ? rect.height() < rect.width() : rect.height() > rect.width();
    switch (rotation) {
    case 0:
        return isTallDisplay ? WebScreenOrientationPortraitPrimary : WebScreenOrientationLandscapePrimary;
    case 90:
        return isTallDisplay ? WebScreenOrientationLandscapePrimary : WebScreenOrientationPortraitSecondary;
    case 180:
        return isTallDisplay ? WebScreenOrientationPortraitSecondary : WebScreenOrientationLandscapeSecondary;
    case 270:
        return isTallDisplay ? WebScreenOrientationLandscapeSecondary : WebScreenOrientationPortraitPrimary;
    default:
        ASSERT_NOT_REACHED();
        return WebScreenOrientationPortraitPrimary;
    }
}

void ScreenOrientationController::updateOrientation()
{
    ASSERT(m_orientation);

    WebScreenOrientationType orientationType = screenOrientationType(m_frame.view());
    if (orientationType == WebScreenOrientationUndefined) {
        // The embedder could not provide us with an orientation, deduce it ourselves.
        orientationType = computeOrientation(m_frame.view());
    }
    ASSERT(orientationType != WebScreenOrientationUndefined);

    m_orientation->setType(orientationType);
    m_orientation->setAngle(screenOrientationAngle(m_frame.view()));
}

void ScreenOrientationController::pageVisibilityChanged()
{
    notifyDispatcher();

    if (!m_orientation || !page() || page()->visibilityState() != PageVisibilityStateVisible)
        return;

    // The orientation type and angle are tied in a way that if the angle has
    // changed, the type must have changed.
    unsigned short currentAngle = screenOrientationAngle(m_frame.view());

    // FIXME: sendOrientationChangeEvent() currently send an event all the
    // children of the frame, so it should only be called on the frame on
    // top of the tree. We would need the embedder to call
    // sendOrientationChangeEvent on every WebFrame part of a WebView to be
    // able to remove this.
    if (m_frame == m_frame.localFrameRoot() && m_orientation->angle() != currentAngle)
        notifyOrientationChanged();
}

void ScreenOrientationController::notifyOrientationChanged()
{
    ASSERT(RuntimeEnabledFeatures::screenOrientationEnabled());

    if (!m_orientation || !page() || page()->visibilityState() != PageVisibilityStateVisible)
        return;

    updateOrientation();

    // Keep track of the frames that need to be notified before notifying the
    // current frame as it will prevent side effects from the change event
    // handlers.
    Vector<RefPtr<LocalFrame> > childFrames;
    for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
        if (child->isLocalFrame())
            childFrames.append(toLocalFrame(child));
    }

    // Notify current orientation object.
    if (!m_dispatchEventTimer.isActive())
        m_dispatchEventTimer.startOneShot(0, FROM_HERE);

    // ... and child frames, if they have a ScreenOrientationController.
    for (size_t i = 0; i < childFrames.size(); ++i) {
        ScreenOrientationController* controller = ScreenOrientationController::from(*childFrames[i]);
        if (controller)
            controller->notifyOrientationChanged();
    }
}

void ScreenOrientationController::setOrientation(ScreenOrientation* orientation)
{
    m_orientation = orientation;
    if (m_orientation)
        updateOrientation();
    notifyDispatcher();
}

void ScreenOrientationController::lock(WebScreenOrientationLockType orientation, WebLockOrientationCallback* callback)
{
    ASSERT(m_client);
    m_client->lockOrientation(orientation, callback);
}

void ScreenOrientationController::unlock()
{
    ASSERT(m_client);
    m_client->unlockOrientation();
}

const LocalFrame& ScreenOrientationController::frame() const
{
    return m_frame;
}

void ScreenOrientationController::dispatchEventTimerFired(Timer<ScreenOrientationController>*)
{
    if (!m_orientation)
        return;
    m_orientation->dispatchEvent(Event::create(EventTypeNames::change));
}

void ScreenOrientationController::didUpdateData()
{
    // Do nothing.
}

void ScreenOrientationController::registerWithDispatcher()
{
    ScreenOrientationDispatcher::instance().addController(this);
}

void ScreenOrientationController::unregisterWithDispatcher()
{
    ScreenOrientationDispatcher::instance().removeController(this);
}

bool ScreenOrientationController::hasLastData()
{
    return true;
}

void ScreenOrientationController::notifyDispatcher()
{
    if (m_orientation && page()->visibilityState() == PageVisibilityStateVisible)
        startUpdating();
    else
        stopUpdating();
}

void ScreenOrientationController::trace(Visitor* visitor)
{
    visitor->trace(m_orientation);
    WillBeHeapSupplement<LocalFrame>::trace(visitor);
}

} // namespace blink
