/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "FakeInputReaderPolicy.h"

#include <android-base/thread_annotations.h>
#include <gtest/gtest.h>

#include "TestConstants.h"
#include "ui/Rotation.h"

namespace android {

void FakeInputReaderPolicy::assertInputDevicesChanged() {
    waitForInputDevices([](bool devicesChanged) {
        if (!devicesChanged) {
            FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
        }
    });
}

void FakeInputReaderPolicy::assertInputDevicesNotChanged() {
    waitForInputDevices([](bool devicesChanged) {
        if (devicesChanged) {
            FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
        }
    });
}

void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) {
    std::unique_lock lock(mLock);
    base::ScopedLockAssertion assumeLocked(mLock);

    const bool success =
            mStylusGestureNotifiedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
                return mDeviceIdOfNotifiedStylusGesture.has_value();
            });
    ASSERT_TRUE(success) << "Timed out waiting for stylus gesture to be notified";
    ASSERT_EQ(deviceId, *mDeviceIdOfNotifiedStylusGesture);
    mDeviceIdOfNotifiedStylusGesture.reset();
}

void FakeInputReaderPolicy::assertStylusGestureNotNotified() {
    std::scoped_lock lock(mLock);
    ASSERT_FALSE(mDeviceIdOfNotifiedStylusGesture);
}

void FakeInputReaderPolicy::clearViewports() {
    mViewports.clear();
    mConfig.setDisplayViewports(mViewports);
}

std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByUniqueId(
        const std::string& uniqueId) const {
    return mConfig.getDisplayViewportByUniqueId(uniqueId);
}
std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByType(
        ViewportType type) const {
    return mConfig.getDisplayViewportByType(type);
}

std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByPort(
        uint8_t displayPort) const {
    return mConfig.getDisplayViewportByPort(displayPort);
}

void FakeInputReaderPolicy::addDisplayViewport(DisplayViewport viewport) {
    mViewports.push_back(std::move(viewport));
    mConfig.setDisplayViewports(mViewports);
}

void FakeInputReaderPolicy::addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width,
                                               int32_t height, ui::Rotation orientation,
                                               bool isActive, const std::string& uniqueId,
                                               std::optional<uint8_t> physicalPort,
                                               ViewportType type) {
    const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270;
    DisplayViewport v;
    v.displayId = displayId;
    v.orientation = orientation;
    v.logicalLeft = 0;
    v.logicalTop = 0;
    v.logicalRight = isRotated ? height : width;
    v.logicalBottom = isRotated ? width : height;
    v.physicalLeft = 0;
    v.physicalTop = 0;
    v.physicalRight = isRotated ? height : width;
    v.physicalBottom = isRotated ? width : height;
    v.deviceWidth = isRotated ? height : width;
    v.deviceHeight = isRotated ? width : height;
    v.isActive = isActive;
    v.uniqueId = uniqueId;
    v.physicalPort = physicalPort;
    v.type = type;

    addDisplayViewport(v);
}

bool FakeInputReaderPolicy::updateViewport(const DisplayViewport& viewport) {
    size_t count = mViewports.size();
    for (size_t i = 0; i < count; i++) {
        const DisplayViewport& currentViewport = mViewports[i];
        if (currentViewport.displayId == viewport.displayId) {
            mViewports[i] = viewport;
            mConfig.setDisplayViewports(mViewports);
            return true;
        }
    }
    // no viewport found.
    return false;
}

void FakeInputReaderPolicy::addExcludedDeviceName(const std::string& deviceName) {
    mConfig.excludedDeviceNames.push_back(deviceName);
}

void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort,
                                                    uint8_t displayPort) {
    mConfig.inputPortToDisplayPortAssociations.insert({inputPort, displayPort});
}

void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPort,
                                                     const std::string& type) {
    mConfig.deviceTypeAssociations.insert({inputPort, type});
}

void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                                        const std::string& displayUniqueId) {
    mConfig.inputPortToDisplayUniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
}

void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
                                                         const KeyboardLayoutInfo& layoutInfo) {
    mConfig.keyboardLayoutAssociations.insert({inputUniqueId, layoutInfo});
}

void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) {
    mConfig.disabledDevices.insert(deviceId);
}

void FakeInputReaderPolicy::removeDisabledDevice(int32_t deviceId) {
    mConfig.disabledDevices.erase(deviceId);
}

const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const {
    return mConfig;
}

const std::vector<InputDeviceInfo> FakeInputReaderPolicy::getInputDevices() const {
    std::scoped_lock lock(mLock);
    return mInputDevices;
}

TouchAffineTransformation FakeInputReaderPolicy::getTouchAffineTransformation(
        const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) {
    return transform;
}

void FakeInputReaderPolicy::setTouchAffineTransformation(const TouchAffineTransformation t) {
    transform = t;
}

PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(const sp<IBinder>& window) {
    mConfig.pointerCaptureRequest = {window, mNextPointerCaptureSequenceNumber++};
    return mConfig.pointerCaptureRequest;
}

void FakeInputReaderPolicy::setDefaultPointerDisplayId(ui::LogicalDisplayId pointerDisplayId) {
    mConfig.defaultPointerDisplayId = pointerDisplayId;
}

void FakeInputReaderPolicy::setPointerGestureEnabled(bool enabled) {
    mConfig.pointerGesturesEnabled = enabled;
}

float FakeInputReaderPolicy::getPointerGestureMovementSpeedRatio() {
    return mConfig.pointerGestureMovementSpeedRatio;
}

float FakeInputReaderPolicy::getPointerGestureZoomSpeedRatio() {
    return mConfig.pointerGestureZoomSpeedRatio;
}

void FakeInputReaderPolicy::setVelocityControlParams(const VelocityControlParameters& params) {
    mConfig.pointerVelocityControlParameters = params;
    mConfig.wheelVelocityControlParameters = params;
}

void FakeInputReaderPolicy::setStylusButtonMotionEventsEnabled(bool enabled) {
    mConfig.stylusButtonMotionEventsEnabled = enabled;
}

void FakeInputReaderPolicy::setStylusPointerIconEnabled(bool enabled) {
    mConfig.stylusPointerIconEnabled = enabled;
}

void FakeInputReaderPolicy::setIsInputMethodConnectionActive(bool active) {
    mIsInputMethodConnectionActive = active;
}

bool FakeInputReaderPolicy::isInputMethodConnectionActive() {
    return mIsInputMethodConnectionActive;
}

void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
    *outConfig = mConfig;
}

void FakeInputReaderPolicy::notifyInputDevicesChanged(
        const std::vector<InputDeviceInfo>& inputDevices) {
    std::scoped_lock lock(mLock);
    mInputDevices = inputDevices;
    mInputDevicesChanged = true;
    mDevicesChangedCondition.notify_all();
}

std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
        const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) {
    return nullptr;
}

std::string FakeInputReaderPolicy::getDeviceAlias(const InputDeviceIdentifier&) {
    return "";
}

void FakeInputReaderPolicy::waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
    std::unique_lock<std::mutex> lock(mLock);
    base::ScopedLockAssertion assumeLocked(mLock);

    const bool devicesChanged =
            mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
                return mInputDevicesChanged;
            });
    ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
    mInputDevicesChanged = false;
}

void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
    std::scoped_lock lock(mLock);
    mDeviceIdOfNotifiedStylusGesture = deviceId;
    mStylusGestureNotifiedCondition.notify_all();
}

std::optional<DisplayViewport> FakeInputReaderPolicy::getPointerViewportForAssociatedDisplay(
        ui::LogicalDisplayId associatedDisplayId) {
    if (!associatedDisplayId.isValid()) {
        associatedDisplayId = mConfig.defaultPointerDisplayId;
    }
    for (auto& viewport : mViewports) {
        if (viewport.displayId == associatedDisplayId) {
            return std::make_optional(viewport);
        }
    }
    return std::nullopt;
}

} // namespace android
