/*
 * Copyright (C) 2017 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_TAG "BroadcastRadioDefault.module"
#define LOG_NDEBUG 0

#include "BroadcastRadio.h"

#include <log/log.h>

#include "resources.h"

namespace android {
namespace hardware {
namespace broadcastradio {
namespace V1_1 {
namespace implementation {

using V1_0::Band;
using V1_0::BandConfig;
using V1_0::Class;
using V1_0::Deemphasis;
using V1_0::Rds;
using V1_1::IdentifierType;
using V1_1::ProgramSelector;
using V1_1::ProgramType;
using V1_1::Properties;
using V1_1::VendorKeyValue;

using std::lock_guard;
using std::map;
using std::mutex;
using std::vector;

// clang-format off
static const map<Class, ModuleConfig> gModuleConfigs{
    {Class::AM_FM, ModuleConfig({
        "Digital radio mock",
        {  // amFmBands
            AmFmBandConfig({
                Band::AM,
                153,         // lowerLimit
                26100,       // upperLimit
                {5, 9, 10},  // spacings
            }),
            AmFmBandConfig({
                Band::FM,
                65800,           // lowerLimit
                108000,          // upperLimit
                {10, 100, 200},  // spacings
            }),
            AmFmBandConfig({
                Band::AM_HD,
                153,         // lowerLimit
                26100,       // upperLimit
                {5, 9, 10},  // spacings
            }),
            AmFmBandConfig({
                Band::FM_HD,
                87700,   // lowerLimit
                107900,  // upperLimit
                {200},   // spacings
            }),
        },
    })},

    {Class::SAT, ModuleConfig({
        "Satellite radio mock",
        {},  // amFmBands
    })},
};
// clang-format on

BroadcastRadio::BroadcastRadio(Class classId)
    : mClassId(classId), mConfig(gModuleConfigs.at(classId)) {}

bool BroadcastRadio::isSupported(Class classId) {
    return gModuleConfigs.find(classId) != gModuleConfigs.end();
}

Return<void> BroadcastRadio::getProperties(getProperties_cb _hidl_cb) {
    ALOGV("%s", __func__);
    return getProperties_1_1(
        [&](const Properties& properties) { _hidl_cb(Result::OK, properties.base); });
}

Return<void> BroadcastRadio::getProperties_1_1(getProperties_1_1_cb _hidl_cb) {
    ALOGV("%s", __func__);
    Properties prop11 = {};
    auto& prop10 = prop11.base;

    prop10.classId = mClassId;
    prop10.implementor = "Google";
    prop10.product = mConfig.productName;
    prop10.numTuners = 1;
    prop10.numAudioSources = 1;
    prop10.supportsCapture = false;
    prop11.supportsBackgroundScanning = true;
    prop11.supportedProgramTypes = hidl_vec<uint32_t>({
        static_cast<uint32_t>(ProgramType::AM), static_cast<uint32_t>(ProgramType::FM),
        static_cast<uint32_t>(ProgramType::AM_HD), static_cast<uint32_t>(ProgramType::FM_HD),
    });
    prop11.supportedIdentifierTypes = hidl_vec<uint32_t>({
        static_cast<uint32_t>(IdentifierType::AMFM_FREQUENCY),
        static_cast<uint32_t>(IdentifierType::RDS_PI),
        static_cast<uint32_t>(IdentifierType::HD_STATION_ID_EXT),
        static_cast<uint32_t>(IdentifierType::HD_SUBCHANNEL),
    });
    prop11.vendorInfo = hidl_vec<VendorKeyValue>({
        {"com.google.dummy", "dummy"},
    });

    prop10.bands = getAmFmBands();

    _hidl_cb(prop11);
    return Void();
}

Return<void> BroadcastRadio::openTuner(const BandConfig& config, bool audio __unused,
                                       const sp<V1_0::ITunerCallback>& callback,
                                       openTuner_cb _hidl_cb) {
    ALOGV("%s(%s)", __func__, toString(config.type).c_str());
    lock_guard<mutex> lk(mMut);

    auto oldTuner = mTuner.promote();
    if (oldTuner != nullptr) {
        ALOGI("Force-closing previously opened tuner");
        oldTuner->forceClose();
        mTuner = nullptr;
    }

    sp<Tuner> newTuner = new Tuner(this, mClassId, callback);
    mTuner = newTuner;
    if (mClassId == Class::AM_FM) {
        auto ret = newTuner->setConfiguration(config);
        if (ret != Result::OK) {
            _hidl_cb(Result::INVALID_ARGUMENTS, {});
            return Void();
        }
    }

    _hidl_cb(Result::OK, newTuner);
    return Void();
}

Return<void> BroadcastRadio::getImage(int32_t id, getImage_cb _hidl_cb) {
    ALOGV("%s(%x)", __func__, id);

    if (id == resources::demoPngId) {
        _hidl_cb(std::vector<uint8_t>(resources::demoPng, std::end(resources::demoPng)));
        return {};
    }

    ALOGI("Image %x doesn't exists", id);
    _hidl_cb({});
    return Void();
}

std::vector<V1_0::BandConfig> BroadcastRadio::getAmFmBands() const {
    std::vector<V1_0::BandConfig> out;
    for (auto&& src : mConfig.amFmBands) {
        V1_0::BandConfig dst;

        dst.type = src.type;
        dst.antennaConnected = true;
        dst.lowerLimit = src.lowerLimit;
        dst.upperLimit = src.upperLimit;
        dst.spacings = src.spacings;

        if (utils::isAm(src.type)) {
            dst.ext.am.stereo = true;
        } else if (utils::isFm(src.type)) {
            dst.ext.fm.deemphasis = static_cast<Deemphasis>(Deemphasis::D50 | Deemphasis::D75);
            dst.ext.fm.stereo = true;
            dst.ext.fm.rds = static_cast<Rds>(Rds::WORLD | Rds::US);
            dst.ext.fm.ta = true;
            dst.ext.fm.af = true;
            dst.ext.fm.ea = true;
        }

        out.push_back(dst);
    }
    return out;
}

}  // namespace implementation
}  // namespace V1_1
}  // namespace broadcastradio
}  // namespace hardware
}  // namespace android