/*
 * Copyright 2019 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 "PhaseOffsets.h"

#include <cutils/properties.h>

#include "SurfaceFlingerProperties.h"

namespace android {
using namespace android::sysprop;

namespace scheduler {

using RefreshRateType = RefreshRateConfigs::RefreshRateType;
PhaseOffsets::~PhaseOffsets() = default;

namespace impl {
PhaseOffsets::PhaseOffsets() {
    int64_t vsyncPhaseOffsetNs = vsync_event_phase_offset_ns(1000000);

    int64_t sfVsyncPhaseOffsetNs = vsync_sf_event_phase_offset_ns(1000000);

    char value[PROPERTY_VALUE_MAX];
    property_get("debug.sf.early_phase_offset_ns", value, "-1");
    const int earlySfOffsetNs = atoi(value);

    property_get("debug.sf.early_gl_phase_offset_ns", value, "-1");
    const int earlyGlSfOffsetNs = atoi(value);

    property_get("debug.sf.early_app_phase_offset_ns", value, "-1");
    const int earlyAppOffsetNs = atoi(value);

    property_get("debug.sf.early_gl_app_phase_offset_ns", value, "-1");
    const int earlyGlAppOffsetNs = atoi(value);

    property_get("debug.sf.high_fps_early_phase_offset_ns", value, "-1");
    const int highFpsEarlySfOffsetNs = atoi(value);

    property_get("debug.sf.high_fps_early_gl_phase_offset_ns", value, "-1");
    const int highFpsEarlyGlSfOffsetNs = atoi(value);

    property_get("debug.sf.high_fps_early_app_phase_offset_ns", value, "-1");
    const int highFpsEarlyAppOffsetNs = atoi(value);

    property_get("debug.sf.high_fps_early_gl_app_phase_offset_ns", value, "-1");
    const int highFpsEarlyGlAppOffsetNs = atoi(value);

    // TODO(b/122905996): Define these in device.mk.
    property_get("debug.sf.high_fps_late_app_phase_offset_ns", value, "2000000");
    const int highFpsLateAppOffsetNs = atoi(value);

    property_get("debug.sf.high_fps_late_sf_phase_offset_ns", value, "1000000");
    const int highFpsLateSfOffsetNs = atoi(value);

    // Below defines the threshold when an offset is considered to be negative, i.e. targeting
    // for the N+2 vsync instead of N+1. This means that:
    // For offset < threshold, SF wake up (vsync_duration - offset) before HW vsync.
    // For offset >= threshold, SF wake up (2 * vsync_duration - offset) before HW vsync.
    property_get("debug.sf.phase_offset_threshold_for_next_vsync_ns", value, "-1");
    const int phaseOffsetThresholdForNextVsyncNs = atoi(value);

    Offsets defaultOffsets;
    Offsets highFpsOffsets;
    defaultOffsets.early = {RefreshRateType::DEFAULT,
                            earlySfOffsetNs != -1 ? earlySfOffsetNs : sfVsyncPhaseOffsetNs,
                            earlyAppOffsetNs != -1 ? earlyAppOffsetNs : vsyncPhaseOffsetNs};
    defaultOffsets.earlyGl = {RefreshRateType::DEFAULT,
                              earlyGlSfOffsetNs != -1 ? earlyGlSfOffsetNs : sfVsyncPhaseOffsetNs,
                              earlyGlAppOffsetNs != -1 ? earlyGlAppOffsetNs : vsyncPhaseOffsetNs};
    defaultOffsets.late = {RefreshRateType::DEFAULT, sfVsyncPhaseOffsetNs, vsyncPhaseOffsetNs};

    highFpsOffsets.early = {RefreshRateType::PERFORMANCE,
                            highFpsEarlySfOffsetNs != -1 ? highFpsEarlySfOffsetNs
                                                         : highFpsLateSfOffsetNs,
                            highFpsEarlyAppOffsetNs != -1 ? highFpsEarlyAppOffsetNs
                                                          : highFpsLateAppOffsetNs};
    highFpsOffsets.earlyGl = {RefreshRateType::PERFORMANCE,
                              highFpsEarlyGlSfOffsetNs != -1 ? highFpsEarlyGlSfOffsetNs
                                                             : highFpsLateSfOffsetNs,
                              highFpsEarlyGlAppOffsetNs != -1 ? highFpsEarlyGlAppOffsetNs
                                                              : highFpsLateAppOffsetNs};
    highFpsOffsets.late = {RefreshRateType::PERFORMANCE, highFpsLateSfOffsetNs,
                           highFpsLateAppOffsetNs};

    mOffsets.insert({RefreshRateType::POWER_SAVING, defaultOffsets});
    mOffsets.insert({RefreshRateType::DEFAULT, defaultOffsets});
    mOffsets.insert({RefreshRateType::PERFORMANCE, highFpsOffsets});

    mOffsetThresholdForNextVsync = phaseOffsetThresholdForNextVsyncNs != -1
            ? phaseOffsetThresholdForNextVsyncNs
            : std::numeric_limits<nsecs_t>::max();
}

PhaseOffsets::Offsets PhaseOffsets::getOffsetsForRefreshRate(
        android::scheduler::RefreshRateConfigs::RefreshRateType refreshRateType) const {
    return mOffsets.at(refreshRateType);
}

void PhaseOffsets::dump(std::string& result) const {
    const auto [early, earlyGl, late] = getCurrentOffsets();
    base::StringAppendF(&result,
                        "         app phase: %9" PRId64 " ns\t         SF phase: %9" PRId64 " ns\n"
                        "   early app phase: %9" PRId64 " ns\t   early SF phase: %9" PRId64 " ns\n"
                        "GL early app phase: %9" PRId64 " ns\tGL early SF phase: %9" PRId64 " ns\n",
                        late.app, late.sf, early.app, early.sf, earlyGl.app, earlyGl.sf);
}

nsecs_t PhaseOffsets::getCurrentAppOffset() {
    return getCurrentOffsets().late.app;
}

nsecs_t PhaseOffsets::getCurrentSfOffset() {
    return getCurrentOffsets().late.sf;
}

} // namespace impl
} // namespace scheduler
} // namespace android
