/*
 * Copyright 2023 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 <fuzzer/FuzzedDataProvider.h>

namespace android {

namespace {
static constexpr int32_t MAX_RANDOM_POINTERS = 4;
static constexpr int32_t MAX_RANDOM_DEVICES = 4;
} // namespace

int getFuzzedMotionAction(FuzzedDataProvider& fdp) {
    int actionMasked = fdp.PickValueInArray<int>({
            AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_ACTION_UP, AMOTION_EVENT_ACTION_MOVE,
            AMOTION_EVENT_ACTION_HOVER_ENTER, AMOTION_EVENT_ACTION_HOVER_MOVE,
            AMOTION_EVENT_ACTION_HOVER_EXIT, AMOTION_EVENT_ACTION_CANCEL,
            // do not inject AMOTION_EVENT_ACTION_OUTSIDE,
            AMOTION_EVENT_ACTION_SCROLL, AMOTION_EVENT_ACTION_POINTER_DOWN,
            AMOTION_EVENT_ACTION_POINTER_UP,
            // do not send buttons until verifier supports them
            // AMOTION_EVENT_ACTION_BUTTON_PRESS,
            // AMOTION_EVENT_ACTION_BUTTON_RELEASE,
    });
    switch (actionMasked) {
        case AMOTION_EVENT_ACTION_POINTER_DOWN:
        case AMOTION_EVENT_ACTION_POINTER_UP: {
            const int32_t index = fdp.ConsumeIntegralInRange(0, MAX_RANDOM_POINTERS - 1);
            const int32_t action =
                    actionMasked | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
            return action;
        }
        default:
            return actionMasked;
    }
}

/**
 * For now, focus on the 3 main sources.
 */
int getFuzzedSource(FuzzedDataProvider& fdp) {
    return fdp.PickValueInArray<int>({
            // AINPUT_SOURCE_UNKNOWN,
            // AINPUT_SOURCE_KEYBOARD,
            // AINPUT_SOURCE_DPAD,
            // AINPUT_SOURCE_GAMEPAD,
            AINPUT_SOURCE_TOUCHSCREEN, AINPUT_SOURCE_MOUSE, AINPUT_SOURCE_STYLUS,
            // AINPUT_SOURCE_BLUETOOTH_STYLUS,
            // AINPUT_SOURCE_TRACKBALL,
            // AINPUT_SOURCE_MOUSE_RELATIVE,
            // AINPUT_SOURCE_TOUCHPAD,
            // AINPUT_SOURCE_TOUCH_NAVIGATION,
            // AINPUT_SOURCE_JOYSTICK,
            // AINPUT_SOURCE_HDMI,
            // AINPUT_SOURCE_SENSOR,
            // AINPUT_SOURCE_ROTARY_ENCODER,
            // AINPUT_SOURCE_ANY,
    });
}

int getFuzzedButtonState(FuzzedDataProvider& fdp) {
    return fdp.PickValueInArray<int>({
            0,
            // AMOTION_EVENT_BUTTON_PRIMARY,
            // AMOTION_EVENT_BUTTON_SECONDARY,
            // AMOTION_EVENT_BUTTON_TERTIARY,
            // AMOTION_EVENT_BUTTON_BACK,
            // AMOTION_EVENT_BUTTON_FORWARD,
            // AMOTION_EVENT_BUTTON_STYLUS_PRIMARY,
            // AMOTION_EVENT_BUTTON_STYLUS_SECONDARY,
    });
}

int32_t getFuzzedFlags(FuzzedDataProvider& fdp, int32_t action) {
    constexpr std::array<int32_t, 4> FLAGS{
            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
            AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
            AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
            AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
    };

    int32_t flags = 0;
    for (size_t i = 0; i < fdp.ConsumeIntegralInRange(size_t(0), FLAGS.size()); i++) {
        flags |= fdp.PickValueInArray<int32_t>(FLAGS);
    }
    if (action == AMOTION_EVENT_ACTION_CANCEL) {
        flags |= AMOTION_EVENT_FLAG_CANCELED;
    }
    if (MotionEvent::getActionMasked(action) == AMOTION_EVENT_ACTION_POINTER_UP) {
        if (fdp.ConsumeBool()) {
            flags |= AMOTION_EVENT_FLAG_CANCELED;
        }
    }
    return flags;
}

int32_t getFuzzedPointerCount(FuzzedDataProvider& fdp, int32_t action) {
    switch (MotionEvent::getActionMasked(action)) {
        case AMOTION_EVENT_ACTION_DOWN:
        case AMOTION_EVENT_ACTION_UP: {
            return 1;
        }
        case AMOTION_EVENT_ACTION_OUTSIDE:
        case AMOTION_EVENT_ACTION_CANCEL:
        case AMOTION_EVENT_ACTION_MOVE:
            return fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS);
        case AMOTION_EVENT_ACTION_HOVER_ENTER:
        case AMOTION_EVENT_ACTION_HOVER_MOVE:
        case AMOTION_EVENT_ACTION_HOVER_EXIT:
            return 1;
        case AMOTION_EVENT_ACTION_SCROLL:
            return 1;
        case AMOTION_EVENT_ACTION_POINTER_DOWN:
        case AMOTION_EVENT_ACTION_POINTER_UP: {
            const uint8_t actionIndex = MotionEvent::getActionIndex(action);
            const int32_t count =
                    std::max(actionIndex + 1,
                             fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS));
            // Need to have at least 2 pointers
            return std::max(2, count);
        }
        case AMOTION_EVENT_ACTION_BUTTON_PRESS:
        case AMOTION_EVENT_ACTION_BUTTON_RELEASE: {
            return 1;
        }
    }
    return 1;
}

ToolType getToolType(int32_t source) {
    switch (source) {
        case AINPUT_SOURCE_TOUCHSCREEN:
            return ToolType::FINGER;
        case AINPUT_SOURCE_MOUSE:
            return ToolType::MOUSE;
        case AINPUT_SOURCE_STYLUS:
            return ToolType::STYLUS;
    }
    return ToolType::UNKNOWN;
}

inline nsecs_t now() {
    return systemTime(SYSTEM_TIME_MONOTONIC);
}

NotifyMotionArgs generateFuzzedMotionArgs(IdGenerator& idGenerator, FuzzedDataProvider& fdp,
                                          int32_t maxDisplays) {
    // Create a basic motion event for testing
    const int32_t source = getFuzzedSource(fdp);
    const ToolType toolType = getToolType(source);
    const int32_t action = getFuzzedMotionAction(fdp);
    const int32_t pointerCount = getFuzzedPointerCount(fdp, action);
    std::vector<PointerProperties> pointerProperties;
    std::vector<PointerCoords> pointerCoords;
    for (int i = 0; i < pointerCount; i++) {
        PointerProperties properties{};
        properties.id = i;
        properties.toolType = toolType;
        pointerProperties.push_back(properties);

        PointerCoords coords{};
        coords.setAxisValue(AMOTION_EVENT_AXIS_X, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
        coords.setAxisValue(AMOTION_EVENT_AXIS_Y, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1);
        pointerCoords.push_back(coords);
    }

    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1);
    const int32_t deviceId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DEVICES - 1);

    // Current time +- 5 seconds
    const nsecs_t currentTime = now();
    const nsecs_t downTime =
            fdp.ConsumeIntegralInRange<nsecs_t>(currentTime - 5E9, currentTime + 5E9);
    const nsecs_t readTime = downTime;
    const nsecs_t eventTime = fdp.ConsumeIntegralInRange<nsecs_t>(downTime, downTime + 1E9);

    const float cursorX = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
    const float cursorY = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
    return NotifyMotionArgs(idGenerator.nextId(), eventTime, readTime, deviceId, source, displayId,
                            POLICY_FLAG_PASS_TO_USER, action,
                            /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
                            getFuzzedFlags(fdp, action), AMETA_NONE, getFuzzedButtonState(fdp),
                            MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
                            pointerProperties.data(), pointerCoords.data(),
                            /*xPrecision=*/0,
                            /*yPrecision=*/0, cursorX, cursorY, downTime,
                            /*videoFrames=*/{});
}

} // namespace android
