/**
 * Copyright (c) 2021, 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "TunerService"

#include "TunerService.h"

#include <aidl/android/hardware/tv/tuner/IDemux.h>
#include <aidl/android/hardware/tv/tuner/IDescrambler.h>
#include <aidl/android/hardware/tv/tuner/IFrontend.h>
#include <aidl/android/hardware/tv/tuner/ILnb.h>
#include <aidl/android/hardware/tv/tuner/Result.h>
#include <android/binder_manager.h>
#include <binder/IPCThreadState.h>
#include <binder/PermissionCache.h>
#include <cutils/properties.h>
#include <utils/Log.h>

#include <string>

#include "TunerDemux.h"
#include "TunerDescrambler.h"
#include "TunerFrontend.h"
#include "TunerHelper.h"
#include "TunerLnb.h"

using ::aidl::android::hardware::tv::tuner::IDemux;
using ::aidl::android::hardware::tv::tuner::IDescrambler;
using ::aidl::android::hardware::tv::tuner::IFrontend;
using ::aidl::android::hardware::tv::tuner::Result;
using ::android::IPCThreadState;
using ::android::PermissionCache;
using ::android::sp;

namespace aidl {
namespace android {
namespace media {
namespace tv {
namespace tuner {

TunerService::TunerService() {
    const string statsServiceName = string() + ITuner::descriptor + "/default";
    ::ndk::SpAIBinder binder(AServiceManager_waitForService(statsServiceName.c_str()));
    mTuner = ITuner::fromBinder(binder);
    ALOGE_IF(mTuner == nullptr, "Failed to get Tuner HAL Service");

    mTunerVersion = TUNER_HAL_VERSION_2_0;
    if (mTuner->getInterfaceVersion(&mTunerVersion).isOk()) {
        // Tuner AIDL HAL version 1 will be Tuner HAL 2.0
        mTunerVersion = (mTunerVersion + 1) << 16;
    }

    // Register the tuner resources to TRM.
    updateTunerResources();
}

TunerService::~TunerService() {
    mTuner = nullptr;
}

binder_status_t TunerService::instantiate() {
    shared_ptr<TunerService> tunerService = ::ndk::SharedRefBase::make<TunerService>();
    bool lazyHal = property_get_bool("ro.tuner.lazyhal", false);
    if (lazyHal) {
        return AServiceManager_registerLazyService(tunerService->asBinder().get(),
                                                   getServiceName());
    }
    return AServiceManager_addService(tunerService->asBinder().get(), getServiceName());
}

::ndk::ScopedAStatus TunerService::openDemux(int32_t in_demuxHandle,
                                             shared_ptr<ITunerDemux>* _aidl_return) {
    ALOGV("openDemux");
    shared_ptr<IDemux> demux;
    bool fallBackToOpenDemux = false;
    vector<int32_t> ids;

    if (mTunerVersion <= TUNER_HAL_VERSION_2_0) {
        fallBackToOpenDemux = true;
    } else {
        mTuner->getDemuxIds(&ids);
        if (ids.size() == 0) {
            fallBackToOpenDemux = true;
        }
    }

    if (fallBackToOpenDemux) {
        auto status = mTuner->openDemux(&ids, &demux);
        if (status.isOk()) {
            *_aidl_return = ::ndk::SharedRefBase::make<TunerDemux>(demux, ids[0],
                                                                   this->ref<TunerService>());
        }
        return status;
    } else {
        int id = TunerHelper::getResourceIdFromHandle(in_demuxHandle, DEMUX);
        auto status = mTuner->openDemuxById(id, &demux);
        if (status.isOk()) {
            *_aidl_return =
                    ::ndk::SharedRefBase::make<TunerDemux>(demux, id, this->ref<TunerService>());
        }
        return status;
    }
}

::ndk::ScopedAStatus TunerService::getDemuxInfo(int32_t in_demuxHandle, DemuxInfo* _aidl_return) {
    if (mTunerVersion <= TUNER_HAL_VERSION_2_0) {
        return ::ndk::ScopedAStatus::fromServiceSpecificError(
                static_cast<int32_t>(Result::UNAVAILABLE));
    }
    int id = TunerHelper::getResourceIdFromHandle(in_demuxHandle, DEMUX);
    return mTuner->getDemuxInfo(id, _aidl_return);
}

::ndk::ScopedAStatus TunerService::getDemuxInfoList(vector<DemuxInfo>* _aidl_return) {
    if (mTunerVersion <= TUNER_HAL_VERSION_2_0) {
        return ::ndk::ScopedAStatus::fromServiceSpecificError(
                static_cast<int32_t>(Result::UNAVAILABLE));
    }
    vector<DemuxInfo> demuxInfoList;
    vector<int32_t> ids;
    auto status = mTuner->getDemuxIds(&ids);
    if (!status.isOk()) {
        return ::ndk::ScopedAStatus::fromServiceSpecificError(
                static_cast<int32_t>(Result::UNAVAILABLE));
    }

    for (int i = 0; i < ids.size(); i++) {
        DemuxInfo demuxInfo;
        auto res = mTuner->getDemuxInfo(ids[i], &demuxInfo);
        if (!res.isOk()) {
            continue;
        }
        demuxInfoList.push_back(demuxInfo);
    }

    if (demuxInfoList.size() > 0) {
        *_aidl_return = demuxInfoList;
        return ::ndk::ScopedAStatus::ok();
    } else {
        return ::ndk::ScopedAStatus::fromServiceSpecificError(
                static_cast<int32_t>(Result::UNAVAILABLE));
    }
}

::ndk::ScopedAStatus TunerService::getDemuxCaps(DemuxCapabilities* _aidl_return) {
    ALOGV("getDemuxCaps");
    return mTuner->getDemuxCaps(_aidl_return);
}

::ndk::ScopedAStatus TunerService::getFrontendIds(vector<int32_t>* ids) {
    return mTuner->getFrontendIds(ids);
}

::ndk::ScopedAStatus TunerService::getFrontendInfo(int32_t id, FrontendInfo* _aidl_return) {
    return mTuner->getFrontendInfo(id, _aidl_return);
}

::ndk::ScopedAStatus TunerService::openFrontend(int32_t frontendHandle,
                                                shared_ptr<ITunerFrontend>* _aidl_return) {
    int id = TunerHelper::getResourceIdFromHandle(frontendHandle, FRONTEND);
    shared_ptr<IFrontend> frontend;
    auto status = mTuner->openFrontendById(id, &frontend);
    if (status.isOk()) {
        *_aidl_return = ::ndk::SharedRefBase::make<TunerFrontend>(frontend, id);
    }

    return status;
}

::ndk::ScopedAStatus TunerService::openLnb(int lnbHandle, shared_ptr<ITunerLnb>* _aidl_return) {
    shared_ptr<ILnb> lnb;
    int id = TunerHelper::getResourceIdFromHandle(lnbHandle, LNB);
    auto status = mTuner->openLnbById(id, &lnb);
    if (status.isOk()) {
        *_aidl_return = ::ndk::SharedRefBase::make<TunerLnb>(lnb, id);
    }

    return status;
}

::ndk::ScopedAStatus TunerService::openLnbByName(const string& lnbName,
                                                 shared_ptr<ITunerLnb>* _aidl_return) {
    vector<int32_t> id;
    shared_ptr<ILnb> lnb;
    auto status = mTuner->openLnbByName(lnbName, &id, &lnb);
    if (status.isOk()) {
        *_aidl_return = ::ndk::SharedRefBase::make<TunerLnb>(lnb, id[0]);
    }

    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus TunerService::openDescrambler(int32_t /*descramblerHandle*/,
                                                   shared_ptr<ITunerDescrambler>* _aidl_return) {
    shared_ptr<IDescrambler> descrambler;
    // int id = TunerHelper::getResourceIdFromHandle(descramblerHandle, DESCRAMBLER);
    auto status = mTuner->openDescrambler(&descrambler);
    if (status.isOk()) {
        *_aidl_return = ::ndk::SharedRefBase::make<TunerDescrambler>(descrambler);
    }

    return status;
}

::ndk::ScopedAStatus TunerService::getTunerHalVersion(int* _aidl_return) {
    *_aidl_return = mTunerVersion;
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus TunerService::openSharedFilter(const string& in_filterToken,
                                                    const shared_ptr<ITunerFilterCallback>& in_cb,
                                                    shared_ptr<ITunerFilter>* _aidl_return) {
    if (!PermissionCache::checkCallingPermission(sSharedFilterPermission)) {
        ALOGE("Request requires android.permission.ACCESS_TV_SHARED_FILTER");
        return ::ndk::ScopedAStatus::fromServiceSpecificError(
                static_cast<int32_t>(Result::UNAVAILABLE));
    }

    Mutex::Autolock _l(mSharedFiltersLock);
    if (mSharedFilters.find(in_filterToken) == mSharedFilters.end()) {
        *_aidl_return = nullptr;
        ALOGD("fail to find %s", in_filterToken.c_str());
        return ::ndk::ScopedAStatus::fromServiceSpecificError(
                static_cast<int32_t>(Result::INVALID_STATE));
    }

    shared_ptr<TunerFilter> filter = mSharedFilters.at(in_filterToken);
    IPCThreadState* ipc = IPCThreadState::self();
    const int pid = ipc->getCallingPid();
    if (!filter->isSharedFilterAllowed(pid)) {
        *_aidl_return = nullptr;
        ALOGD("shared filter %s is opened in the same process", in_filterToken.c_str());
        return ::ndk::ScopedAStatus::fromServiceSpecificError(
                static_cast<int32_t>(Result::INVALID_STATE));
    }

    filter->attachSharedFilterCallback(in_cb);

    *_aidl_return = filter;
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus TunerService::isLnaSupported(bool* _aidl_return) {
    ALOGV("isLnaSupported");
    return mTuner->isLnaSupported(_aidl_return);
}

::ndk::ScopedAStatus TunerService::setLna(bool bEnable) {
    return mTuner->setLna(bEnable);
}

::ndk::ScopedAStatus TunerService::setMaxNumberOfFrontends(FrontendType in_frontendType,
                                                           int32_t in_maxNumber) {
    return mTuner->setMaxNumberOfFrontends(in_frontendType, in_maxNumber);
}

::ndk::ScopedAStatus TunerService::getMaxNumberOfFrontends(FrontendType in_frontendType,
                                                           int32_t* _aidl_return) {
    return mTuner->getMaxNumberOfFrontends(in_frontendType, _aidl_return);
}

string TunerService::addFilterToShared(const shared_ptr<TunerFilter>& sharedFilter) {
    Mutex::Autolock _l(mSharedFiltersLock);

    // Use sharedFilter address as token.
    string token = to_string(reinterpret_cast<std::uintptr_t>(sharedFilter.get()));
    mSharedFilters[token] = sharedFilter;
    return token;
}

void TunerService::removeSharedFilter(const shared_ptr<TunerFilter>& sharedFilter) {
    Mutex::Autolock _l(mSharedFiltersLock);

    // Use sharedFilter address as token.
    mSharedFilters.erase(to_string(reinterpret_cast<std::uintptr_t>(sharedFilter.get())));
}

void TunerService::updateTunerResources() {
    TunerHelper::updateTunerResources(getTRMFrontendInfos(),
                                      getTRMDemuxInfos(),
                                      getTRMLnbHandles());
}

vector<TunerFrontendInfo> TunerService::getTRMFrontendInfos() {
    vector<TunerFrontendInfo> infos;
    vector<int32_t> ids;
    auto status = mTuner->getFrontendIds(&ids);
    if (!status.isOk()) {
        return infos;
    }

    for (int i = 0; i < ids.size(); i++) {
        FrontendInfo frontendInfo;
        auto res = mTuner->getFrontendInfo(ids[i], &frontendInfo);
        if (!res.isOk()) {
            continue;
        }
        TunerFrontendInfo tunerFrontendInfo{
                .handle = TunerHelper::getResourceHandleFromId((int)ids[i], FRONTEND),
                .type = static_cast<int>(frontendInfo.type),
                .exclusiveGroupId = frontendInfo.exclusiveGroupId,
        };
        infos.push_back(tunerFrontendInfo);
    }

    return infos;
}

vector<TunerDemuxInfo> TunerService::getTRMDemuxInfos() {
    vector<TunerDemuxInfo> infos;
    vector<int32_t> ids;

    if (mTunerVersion <= TUNER_HAL_VERSION_2_0) {
        return infos;
    }

    auto status = mTuner->getDemuxIds(&ids);
    if (!status.isOk()) {
        return infos;
    }

    for (int i = 0; i < ids.size(); i++) {
        DemuxInfo demuxInfo;
        mTuner->getDemuxInfo(ids[i], &demuxInfo);
        TunerDemuxInfo tunerDemuxInfo{
                .handle = TunerHelper::getResourceHandleFromId((int)ids[i], DEMUX),
                .filterTypes = static_cast<int>(demuxInfo.filterTypes)
        };
        infos.push_back(tunerDemuxInfo);
    }

    return infos;
}

vector<int32_t> TunerService::getTRMLnbHandles() {
    vector<int32_t> lnbHandles;
    if (mTuner != nullptr) {
        vector<int32_t> lnbIds;
        auto res = mTuner->getLnbIds(&lnbIds);
        if (res.isOk()) {
            for (int i = 0; i < lnbIds.size(); i++) {
                lnbHandles.push_back(TunerHelper::getResourceHandleFromId(lnbIds[i], LNB));
            }
        }
    }

    return lnbHandles;
}

}  // namespace tuner
}  // namespace tv
}  // namespace media
}  // namespace android
}  // namespace aidl